From e01a2fcb4a75933dd5d2b957018950992363a20b Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 28 Jul 2021 09:03:45 -0700 Subject: [PATCH 001/108] feat: initial work for an emerging multi token standard This features the traits and proposal background information required have a proper specification for multiple tokens in a contract, that can be symmetric with other multi token contracts across chains. --- specs/Standards/MultiToken/README.md | 538 +++++++++++++++++++++++++++ 1 file changed, 538 insertions(+) create mode 100644 specs/Standards/MultiToken/README.md diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md new file mode 100644 index 000000000..b18eb2700 --- /dev/null +++ b/specs/Standards/MultiToken/README.md @@ -0,0 +1,538 @@ +- Proposal Name: Multi-Token-Standard +- Start Date: 2021/07/24 +- Issue(s): #227. +# Summary +[summary]: #summary + +A standard interface for a multi token standard that supports fungible, semi-fungible, and tokens of any type, allowing for ownership, transfer, and batch transfer of tokens generally regardless of specific type. + +# Motivation +[motivation]: #motivation +Having a single contract represent both NFTs and FTs can greatly improve efficiency as demonstrated by Enjin Coin. The ability to make batch requests with multiple asset classes can reduce a many transactions transaciton to a single transaction to trade around both NFTs and FTs that are a part of same token contract. + +Having this will also increase Near's ability to work interoperably with other chains. This will reduce the complexity required to represent these emerging asset classes. + + +Prior art: +- EIP-1155 : https://github.com/ethereum/EIPs/issues/1155 + +- This NEP derives some examples and format from: https://github.com/near/NEPs/pull/21 + +- Example of an NFT Series: https://github.com/near-apps/gnr8 + +- Things from the NFT Discussions: https://github.com/near/NEPs/discussions/171 + +- Things from the NFT Discussions: https://gov.near.org/t/nft-standard-discussion/853 + +Discussions out of band: + - https://gov.near.org/t/multi-token-standard-discussion/2917 + - https://github.com/shipsgold/multi-token-standard-impl/tree/main/meetings + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +We should be able to do the following: +- Get balance of token per user +- Get balance in batch of a user +- Get supply of tokens per id +- Get supply of tokens in batch per id +- Represent non fungibility of tokens +- Represent fungibility of tokens +- Transfer tokens by id in batch +- Use these tokens on an exchange +- Refund storage costs for fungible tokens + +There are a few concepts in the scenarios above: +- **Total supply**. It's the total number of tokens in circulation. +- **Balance owner**. An account ID that owns some amount of tokens. +- **Transfer**. Moves some amount from one account to another account. +- **Fungibility**. An indistinguishable amount of tokens to exchange +- **Non Fungibility**. Tokens that are differentiable from each other. + +### Real scenarios + +#### Simple transfer + +Alice wants to send 5 gold tokens to Bob. + +Let's assume the following: +- The `gold` token is represented by the `games` with token_id `g133`. +- Alice's account is `alice`. +- Bob's account is `bob`. +- The precision for `gold` on the games contract is `10^8`. +- The 5 tokens is `5 * 10^8` or as a number is `500000000`. + +High-level explanation: + +Alice needs to issue one transaction to `games` contract to transfer 5 tokens (multiplied by precision) to Bob. + +Technical calls: + +1. `alice` calls `games::mt_transfer({"receiver_id": "bob", "amount": "500000000", "token_id": "g133"})`. + +#### Simple batch transfer + +Alice wants to send and a 1 unique(nft) gemstone, 5 gold and 10 silver tokens to Bob. + +Let's assume the following: +- The unique nft `gem` token is represented by `games` with token_id +`uu2` +- The `gold` token is represented by the `games` with token_id `g133`. +- The `silver` token is represented by the `games` with token_id `s133`. +- Alice's account is `alice`. +- Bob's account is `bob`. +- The precision for `gold` on the games contract is `10^8`. +- The precision for `silver` on the games contract is also `10^8`. +- The 5 gold tokens is `5 * 10^8` or as a number is `500000000`. +- The 10 silver tokens is `10 * 10^8` or as a number is `1000000000`. +- The 1 gem token is `1` or as a number is `1` + +High-level explanation: + +Alice needs to issue one transaction to `games` contract to transfer 5 gold tokens and 10 silver tokens (multiplied by precision) and 1 gem to Bob. + +Technical calls: + +1. `alice` calls `games::mt_transfer_batch({"receiver_id": "bob", "amounts": ["500000000", "1000000000", "1"], "token_ids": ["g133", "s133", "uu2"]})`. + + + +#### Token deposit to a contract + +Alice wants to deposit `gold` tokens to a compound interest contract to earn some rewards. + +Let's assume the following: +- The `gold` token is represented by the `games` contract with token_id `g133` . +- Alice's account is `alice`. +- The compound interest contract is `compound`. +- The precision on `gold` token is `10^18`. +- The 1000 tokens is `1000 * 10^18` or as a number is `1000000000000000000000`. +- The compound contract can work with many different token contracts and types. + +High-level explanation: + +Alice needs to issue a single transaction to `games` that will internally issue a cross contract call to `compound`. + +The initial transaction to `games` is made with `compound` as the receiver of a set token_ids and amounts from `alice`. + +This call then waits on a response from `compound`. If `compound` responds with failure, the tx is aborted. + +Otherwise `games` contract accepts the results and resolves the promise completing the transaction. + +- If transfer succeeded, `compound` can increase local ownership for `alice` to 1000 for `gold` token_id `g133` + +- If transfer fails, `compound` doesn't need to do anything in current example, but maybe can notify `alice` of unsuccessful transfer. + +Technical calls: +1. `alice` calls `games::mt_transfer_call({"receiver_id": "compound", amount: "1000000000000000000000", "token_id": "g133", msg: "interest-building"})`. + During the `mt_transfer_call` call, `compound` does the following: + fn mt_on_transfer( + &mut self, + sender_id: AccountId, + token_ids: Vec, + amounts: Vec, + msg: String, + ) -> PromiseOrValue>; +} + 1. calls `compound::mt_on_transfer({"sender_id": "alice", "token_ids":["g133"], "amounts": ["1000000000000000000000"], msg: "interest-building"})`. + 2. `compound` resolves the request/fails and `games` contract handles response from the promise with `games::mt_resolve_transfer` returning refunded amount if there is any or handling follow up from the result of compound cross contract call + +#### Batch Token deposit to a contract + +Alice wants to deposit `silver` and `gold` tokens and the nft `gem` to a compound interest contract to earn some rewards. + +Let's assume the following: +- The `gold` token is represented by the `games` contract with token_id `g133` . +- The `silver` token is represented by the `games` contract with token_id `s133` . +- The `gem` unique only one nft token is represented by the `games` contract with token_id `uu2` . +- Alice's account is `alice`. +- The compound interest contract is `compound`. +- The precision on `gold` token is `10^18`. +- The precision on `silver` token is `10^18`. +- The 1000 tokens is `1000 * 10^18` or as a number is `1000000000000000000000`. +- The compound contract can work with many different token contracts and types. + +High-level explanation: + +Alice needs to issue a single transaction to `games` that will internally issue a cross contract call to `compound`. + +The initial transaction to `games` is made with `compound` as the receiver of a set token_ids and amounts from `alice`. + +This call then waits on a response from `compound`. If `compound` responds with failure, the tx is aborted. + +Otherwise `games` contract accepts the results and resolves the promise completing the transaction. + +- If transfer succeeded, `compound` can increase local ownership for `alice` to 1000 for `gold` token_id `g133` + +- If transfer fails, `compound` doesn't need to do anything in current example, but maybe can notify `alice` of unsuccessful transfer. + +Technical calls: +1. `alice` calls `games::mt_transfer_batch_call({"receiver_id": "compound", amounts: ["1000000000000000000000","1000000000000000000000", "1"], "token_ids": ["g133","s133","uu2"], msg: "interest-building"})`. + During the `mt_transfer_call` call, `compound` does the following: + fn mt_on_transfer( + &mut self, + sender_id: AccountId, + token_ids: Vec, + amounts: Vec, + msg: String, + ) -> PromiseOrValue>; +} + 1. calls `compound::mt_on_transfer({"sender_id": "alice", amounts: ["1000000000000000000000","1000000000000000000000", "1"], "token_ids": ["g133","s133","uu2"], msg: "interest-building"})` + 2. `compound` resolves the request/fails and `games` contract handles response from the promise with `games::mt_resolve_transfer` returning refunded amount if there is any or handling follow up from the result of compound cross contract call + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation +WIP implementation: https://github.com/shipsgold/multi-token-standard-impl/tree/feat/initial-token +### Core Trait +``` +pub trait MultiTokenCore { + /// Basic token transfer. Transfer a token or tokens given a token_id. The token id can correspond to + /// either a NonFungibleToken or Fungible Token this is differeniated by the implementation. + /// + /// Requirements + /// * Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes + /// * Contract MUST panic if called by someone other than token owner or, + /// * If using Approval Management, contract MUST nullify approved accounts on + /// successful transfer. + /// * TODO: needed? Both accounts must be registered with the contract for transfer to + /// succeed. See see https://nomicon.io/Standards/StorageManagement.html + /// + /// Arguments: + /// * `receiver_id`: the valid NEAR account receiving the token + /// * `token_id`: the token or tokens to transfer + /// * `amount`: the token amount of tokens to transfer for token_id + /// * `memo` (optional): for use cases that may benefit from indexing or + /// providing information for a transfer + fn mt_transfer( + &mut self, + receiver_id: AccountId, + token_id: TokenId, + amount: U128, + memo: Option, + ); + + /// Transfer token/s and call a method on a receiver contract. A successful + /// workflow will end in a success execution outcome to the callback on the MultiToken + /// contract at the method `mt_resolve_transfer`. + /// + /// You can think of this as being similar to attaching tokens to a + /// function call. It allows you to attach any Fungible or Non Fungible Token in a call to a + /// receiver contract. + /// + /// Requirements: + /// * Caller of the method must attach a deposit of 1 yoctoⓃ for security + /// purposes + /// * Contract MUST panic if called by someone other than token owner or, + /// if using Approval Management, one of the approved accounts + /// * The receiving contract must implement `mt_on_transfer` according to the + /// standard. If it does not, MultiToken contract's `mt_resolve_transfer` MUST deal + /// with the resulting failed cross-contract call and roll back the transfer. + /// * Contract MUST implement the behavior described in `mt_resolve_transfer` + /// + /// Arguments: + /// * `receiver_id`: the valid NEAR account receiving the token. + /// * `token_id`: the token to send. + /// * `amount`: amount of tokens to transfer for token_id + /// * `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. + fn mt_transfer_call( + &mut self, + receiver_id: AccountId, + token_id: TokenId, + amount: U128, + memo: Option, + msg: String, + ) -> PromiseOrValue; + + /// Batch token transfer. Transfer a tokens given token_ids and amounts. The token ids can correspond to + /// either Non-Fungible Tokens or Fungible Tokens or some combination of the two. The token ids + /// are used to segment the types on a per contract implementation basis. + /// + /// Requirements + /// * Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes + /// * Contract MUST panic if called by someone other than token owner or, + /// if using Approval Management, one of the approved accounts + /// * `approval_id` is for use with Approval Management, + /// see https://nomicon.io/Standards/NonFungibleToken/ApprovalManagement.html + /// * If using Approval Management, contract MUST nullify approved accounts on + /// successful transfer. + /// * TODO: needed? Both accounts must be registered with the contract for transfer to + /// succeed. See see https://nomicon.io/Standards/StorageManagement.html + /// * The token_ids vec and amounts vec must be of equal length and equate to a 1-1 mapping + /// between amount and id. In the event that they do not line up the call should fail + /// + /// Arguments: + /// * `receiver_id`: the valid NEAR account receiving the token + /// * `token_ids`: the tokens to transfer + /// * `amounts`: the amount of tokens to transfer for corresponding token_id + /// * `approval_ids`: expected approval ID. A number smaller than + /// 2^53, and therefore representable as JSON. See Approval Management + /// standard for full explanation. Must have same length as token_ids + /// * `memo` (optional): for use cases that may benefit from indexing or + /// providing information for a transfer + + fn mt_batch_transfer( + &mut self, + receiver_id: AccountId, + token_ids: Vec, + amounts: Vec, + memo: Option, + ); + /// Batch transfer token/s and call a method on a receiver contract. A successful + /// workflow will end in a success execution outcome to the callback on the MultiToken + /// contract at the method `mt_resolve_batch_transfer`. + /// + /// You can think of this as being similar to attaching tokens to a + /// function call. It allows you to attach any Fungible or Non Fungible Token in a call to a + /// receiver contract. + /// + /// Requirements: + /// * Caller of the method must attach a deposit of 1 yoctoⓃ for security + /// purposes + /// * Contract MUST panic if called by someone other than token owner or, + /// if using Approval Management, one of the approved accounts + /// * The receiving contract must implement `mt_on_transfer` according to the + /// standard. If it does not, MultiToken contract's `mt_resolve_batch_transfer` MUST deal + /// with the resulting failed cross-contract call and roll back the transfer. + /// * Contract MUST implement the behavior described in `mt_resolve_batch_transfer` + /// * `approval_id` is for use with Approval Management extension, see + /// that document for full explanation. + /// * If using Approval Management, contract MUST nullify approved accounts on + /// successful transfer. + /// + /// Arguments: + /// * `receiver_id`: the valid NEAR account receiving the token. + /// * `token_ids`: the tokens to transfer + /// * `amounts`: the amount of tokens to transfer for corresponding token_id + /// * `approval_ids`: expected approval IDs. A number smaller than + /// 2^53, and therefore representable as JSON. See Approval Management + /// standard for full explanation. Must have same length as token_ids + /// * `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. + + fn mt_batch_transfer_call( + &mut self, + receiver_id: AccountId, + token_ids: Vec, + amounts: Vec, + memo: Option, + msg: String, + ) -> PromiseOrValue>; + + /// Get the balance of an an account given token_id. For fungible token returns back amount, for + /// non fungible token it returns back constant 1. + fn balance_of(&self, owner_id: AccountId, token_id: TokenId) -> U128; + + /// Get the balances of an an account given token_ids. For fungible token returns back amount, for + /// non fungible token it returns back constant 1. returns vector of balances corresponding to token_ids + /// in a 1-1 mapping + fn balance_of_batch(&self, owner_id: AccountId, token_ids: Vec) -> Vec; + + /// Returns the total supply of the token in a decimal string representation given token_id. + fn total_supply(&self, token_id: TokenId) -> U128; + + // Returns the total supplies of the tokens given by token_ids in a decimal string representation. + fn total_supply_batch(&self, token_ids: Vec) -> Vec; +} +``` +### Receiver Trait +#### Notes +- TokenId is of type String +``` +pub trait MultiTokenReceiver { + /// Take some action after receiving a MultiToken-tokens token + /// + /// Requirements: + /// * Contract MUST restrict calls to this function to a set of whitelisted MultiToken + /// contracts + /// + /// Arguments: + /// * `sender_id`: the sender of `mt_transfer_call` + /// * `previous_owner_id`: the account that owned the tokens prior to it being + /// transferred to this contract, which can differ from `sender_id` if using + /// Approval Management extension + /// * `token_ids`: the `token_ids` argument given to `mt_transfer_call` + /// * `msg`: information necessary for this contract to know how to process the + /// request. This may include method names and/or arguments. + /// + /// Returns true if tokens should be returned to `sender_id` + fn mt_on_transfer( + &mut self, + sender_id: AccountId, + token_ids: Vec, + amounts: Vec, + msg: String, + ) -> PromiseOrValue>; +} +``` +### Resolver Trait +#### Notes +- TokenId is of type String +``` +/// Used when MultiTokens are transferred using `mt_transfer_call`. This is the method that's called after `mt_on_transfer`. This trait is implemented on the MultiToken contract. +pub trait MultiTokenResolver { + /// Finalize an `mt_transfer_call` chain of cross-contract calls. + /// + /// The `mt_transfer_call` process: + /// + /// 1. Sender calls `mt_transfer_call` on MultiToken contract + /// 2. MultiToken contract transfers token from sender to receiver + /// 3. MultiToken contract calls `mt_on_transfer` on receiver contract + /// 4+. [receiver contract may make other cross-contract calls] + /// N. MultiToken contract resolves promise chain with `mt_resolve_transfer`, and may + /// transfer token back to sender + /// + /// Requirements: + /// * Contract MUST forbid calls to this function by any account except self + /// * If promise chain failed, contract MUST revert token transfer + /// * If promise chain resolves with `true`, contract MUST return token to + /// `sender_id` + /// + /// Arguments: + /// * `previous_owner_id`: the owner prior to the call to `mt_transfer_call` + /// * `receiver_id`: the `receiver_id` argument given to `mt_transfer_call` + /// * `token_ids`: the `token_ids` argument given to `mt_transfer_call` + /// * `approvals`: if using Approval Management, contract MUST provide + /// set of original approved accounts in this argument, and restore these + /// approved accounts in case of revert. In this case it may be multiple sets of approvals + /// + /// Returns true if tokens were successfully transferred to `receiver_id`. + fn mt_resolve_transfer( + &mut self, + sender_id: AccountId, + receiver_id: AccountId, + token_ids: Vec, + amounts: Vec, + ) -> Vec; +} +``` +### Storage Management Trait +#### Notes +This is semi necessary for ft token types to be able to refund users for storage of many different token types like gold/silver... this might be slightly out of scope +``` +pub trait StorageManagement { + // if `registration_only=true` MUST refund above the minimum balance if the account didn't exist and + // refund full deposit if the account exists. + fn storage_deposit( + &mut self, + token_ids: Vec, + account_id: Option, + registration_only: Option, + ) -> StorageBalance; + + /// Withdraw specified amount of available Ⓝ for predecessor account. + /// + /// This method is safe to call. It MUST NOT remove data. + /// + /// `amount` is sent as a string representing an unsigned 128-bit integer. If + /// omitted, contract MUST refund full `available` balance. If `amount` exceeds + /// predecessor account's available balance, contract MUST panic. + /// + /// If predecessor account not registered, contract MUST panic. + /// + /// MUST require exactly 1 yoctoNEAR attached balance to prevent restricted + /// function-call access-key call (UX wallet security) + /// + /// Returns the StorageBalance structure showing updated balances. + fn storage_withdraw(&mut self, token_ids:Vec, amount: Option) -> StorageBalance; + + /// Unregisters the predecessor account and returns the storage NEAR deposit back. + /// + /// If the predecessor account is not registered, the function MUST return `false` without panic. + /// + /// If `force=true` the function SHOULD ignore account balances (burn them) and close the account. + /// Otherwise, MUST panic if caller has a positive registered balance (eg token holdings) or + /// the contract doesn't support force unregistration. + /// MUST require exactly 1 yoctoNEAR attached balance to prevent restricted function-call access-key call + /// (UX wallet security) + /// Returns `true` iff the account was unregistered. + /// Returns `false` iff account was not registered before. + fn storage_unregister(&mut self, token_ids:Vec, force: Option) -> Vec; + + fn storage_balance_bounds(&self, token_id:TokenId, account_id: Option) -> StorageBalanceBounds; + fn storage_balance_bounds_batch(&self, token_id:Vec, account_id: Option) -> StorageBalanceBounds; + + fn storage_balance_of(&self, token_id:TokenId, account_id: AccountId) -> Option; + fn storage_balance_of_batch(&self, token_ids:Vec, account_id: AccountId) -> Option; +} +``` + +### Metadata Trait +``` +pub struct MultiTokenMetadata { + pub spec: String, // required, essentially a version like "nft-1.0.0" + pub name: String, // required, ex. "Mosaics" + pub symbol: String, // required, ex. "MOSIAC" + pub icon: Option, // Data URL + pub base_uri: Option, // Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs + // supports metadata_uri interface that interpolates {id} in the string + pub reference: Option, // URL to a JSON file with more info + pub reference_hash: Option, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. +} + +pub struct MultiTokenExtraMetadata { + pub title: Option, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" + pub description: Option, // free-form description + pub media: Option, // URL to associated media, preferably to decentralized, content-addressed storage + pub media_hash: Option, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. + pub copies: Option, // number of copies of this set of metadata in existence when token was minted. + pub issued_at: Option, // ISO 8601 datetime when token was issued or minted + pub expires_at: Option, // ISO 8601 datetime when token expires + pub starts_at: Option, // ISO 8601 datetime when token starts being valid + pub updated_at: Option, // ISO 8601 datetime when token was last updated + pub extra: Option, // anything extra the NFT wants to store on-chain. Can be stringified JSON. + pub reference: Option, // URL to an off-chain JSON file with more info. + pub reference_hash: Option, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. +} + +/// Offers details on the metadata. +pub trait MultiTokenMetadataProvider { + fn mt_metadata(&self, token_id: TokenId) -> MultiTokenMetadata; + fn mt_extra_metadata(&self, token_id: TokenId) -> MultiTokenMetadataExtra; +} +``` +# Drawbacks +[drawbacks]: #drawbacks +Doing this adds another spec and codebase to the standards. It could be seen that we could leave this to developers to implement custom solutions and have them create a token that just uses FT and NFT together. There is some additional complexity +in ux, when considering batch size request and gas limitations, that might trip some developers up. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives +The rationale for this design, is that we want to support developers that need batch requests. In many ecosystems, a single NFT or +series of NFT are not sufficient for representing and managing potentially 100s of tokens. Managing cross contract calls and the token contracts themselves become a huge burden on the developer. This will reduce the complexity, and allow developers to easily bridge over to other contracts like ERC-1155 on other chains that allow for a this style of representation. + +In the design phase it was considered to simply rely on the underlying implementations of fungible tokens and non-fungible tokens +to be the scope of interaction with the chain. Doing this we would have tied the implementations to the bounds of FT and NFT. By loosing this up a bit, we are able to be a bit more flexible in what's possible. + +Not doing this means we really won't have a great way of supporting use cases where developers need to represent and manage +large varying quantities and types of tokens. They will all have to implement it on their own, and that would make for a fragmented +and weak ecosystem. Where every developer would not be able to reliably trade these assets. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions +The unresolved questions, are really what type of metadata , is required for this spec? + +Can we represent events in this spec, which would improve the ecosystem quite a bit? If we represent events what should those events be? + +Should we have a spec for TokenType? + +Should we have a spec for offchain metadata? + +Does the current storage management scheme work for people? + +How freeform should this token be? Right now there is a notion of supply, which is not 100% guaranteed every token has +or wants to track supply semantics. Not having supply makes everything more difficult and requires consumers of the contract +to track minting and burning events. + +Approval Management is probably out of the scope of this solution. + + +# Future possibilities +[future-possibilities]: #future-possibilities +Future possibilities could be around enumeration extension like for the NFT spec, an off chain data spec, approval management if it's required, and error status codes. From d13899fb9d83a35c275510fa96976e8bcebdeab9 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 28 Jul 2021 09:25:49 -0700 Subject: [PATCH 002/108] chore: wrong issue link --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index b18eb2700..33b89dc8e 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -1,6 +1,6 @@ - Proposal Name: Multi-Token-Standard - Start Date: 2021/07/24 -- Issue(s): #227. +- Issue(s): #245. # Summary [summary]: #summary From e5c00d9db2639de61328fcaf0905160bd1f80e3a Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 30 Jul 2021 10:48:01 -0700 Subject: [PATCH 003/108] chore: fix example comment on metadata --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 33b89dc8e..315f8de71 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -466,7 +466,7 @@ pub trait StorageManagement { ### Metadata Trait ``` pub struct MultiTokenMetadata { - pub spec: String, // required, essentially a version like "nft-1.0.0" + pub spec: String, // required, essentially a version like "mt-1.0.0" pub name: String, // required, ex. "Mosaics" pub symbol: String, // required, ex. "MOSIAC" pub icon: Option, // Data URL From f1609fcd010eb179acf41db966f270439dba630b Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 10 Aug 2021 12:43:20 -0700 Subject: [PATCH 004/108] fix: add missing memo field to guide level entry for batch transfer Co-authored-by: marco-sundsk <47070476+marco-sundsk@users.noreply.github.com> --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 315f8de71..c2a20e88f 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -93,7 +93,7 @@ Alice needs to issue one transaction to `games` contract to transfer 5 gold toke Technical calls: -1. `alice` calls `games::mt_transfer_batch({"receiver_id": "bob", "amounts": ["500000000", "1000000000", "1"], "token_ids": ["g133", "s133", "uu2"]})`. +1. `alice` calls `games::mt_transfer_batch({"receiver_id": "bob", "amounts": ["500000000", "1000000000", "1"], "token_ids": ["g133", "s133", "uu2"], "memo": "well done"})`. From f384184c26be6793fe2b657a762977571bed25b2 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 10 Aug 2021 12:43:44 -0700 Subject: [PATCH 005/108] fix: add missing memo field to guide level entry for transfer Co-authored-by: marco-sundsk <47070476+marco-sundsk@users.noreply.github.com> --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index c2a20e88f..cd3db9b7f 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -68,7 +68,7 @@ Alice needs to issue one transaction to `games` contract to transfer 5 tokens (m Technical calls: -1. `alice` calls `games::mt_transfer({"receiver_id": "bob", "amount": "500000000", "token_id": "g133"})`. +1. `alice` calls `games::mt_transfer({"receiver_id": "bob", "amount": "500000000", "token_id": "g133", "memo": "for my dinner"})`. #### Simple batch transfer From d7444861f9169af3ff5c9e5b6640eac5d4b65356 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 10 Aug 2021 14:01:46 -0700 Subject: [PATCH 006/108] fix: consolidate batch versions of balance_of and balance_bounds --- specs/Standards/MultiToken/README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index cd3db9b7f..25cdd3e2a 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -455,11 +455,8 @@ pub trait StorageManagement { /// Returns `false` iff account was not registered before. fn storage_unregister(&mut self, token_ids:Vec, force: Option) -> Vec; - fn storage_balance_bounds(&self, token_id:TokenId, account_id: Option) -> StorageBalanceBounds; - fn storage_balance_bounds_batch(&self, token_id:Vec, account_id: Option) -> StorageBalanceBounds; - - fn storage_balance_of(&self, token_id:TokenId, account_id: AccountId) -> Option; - fn storage_balance_of_batch(&self, token_ids:Vec, account_id: AccountId) -> Option; + fn storage_balance_bounds(&self, token_id:Vec, account_id: Option) -> StorageBalanceBounds; + fn storage_balance_of(&self, token_ids:Vec, account_id: AccountId) -> Option; } ``` From 987eb81a54385036a5d580c1ab98bc2d907f08bc Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 10 Aug 2021 14:05:46 -0700 Subject: [PATCH 007/108] feat: add decimals to the contract to support tokens with precision reqs --- specs/Standards/MultiToken/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 25cdd3e2a..048ec9bd5 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -469,6 +469,7 @@ pub struct MultiTokenMetadata { pub icon: Option, // Data URL pub base_uri: Option, // Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs // supports metadata_uri interface that interpolates {id} in the string + pub decimals: Option, // Option to specify precision if required pub reference: Option, // URL to a JSON file with more info pub reference_hash: Option, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. } From 4390c5c0d5f4ea233b9d6e0d10cd1e0f32c57b96 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 18 Aug 2021 10:07:52 -0700 Subject: [PATCH 008/108] fix: This refactors metadata to be one token metadata. The original thought was that splitting the metadata in two would allow users to more easily represent metadata that correspond to series. That said it was found that developers are more likely to write their own metadata view logic by implementing the metadata provider. The metadata provider is just the interface and the underlying implementation can do the sharing of data the same if its split or not .Therefore we decided to have on metadata interface to respond to to request and to implement. --- specs/Standards/MultiToken/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 048ec9bd5..90dd74388 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -470,11 +470,6 @@ pub struct MultiTokenMetadata { pub base_uri: Option, // Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs // supports metadata_uri interface that interpolates {id} in the string pub decimals: Option, // Option to specify precision if required - pub reference: Option, // URL to a JSON file with more info - pub reference_hash: Option, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. -} - -pub struct MultiTokenExtraMetadata { pub title: Option, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" pub description: Option, // free-form description pub media: Option, // URL to associated media, preferably to decentralized, content-addressed storage @@ -492,7 +487,6 @@ pub struct MultiTokenExtraMetadata { /// Offers details on the metadata. pub trait MultiTokenMetadataProvider { fn mt_metadata(&self, token_id: TokenId) -> MultiTokenMetadata; - fn mt_extra_metadata(&self, token_id: TokenId) -> MultiTokenMetadataExtra; } ``` # Drawbacks From c0596572cf1287aaee132a2f72dda94749f3bd9e Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 18 Aug 2021 10:27:15 -0700 Subject: [PATCH 009/108] feat: add answers to the unresolved questions in the NEP --- specs/Standards/MultiToken/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 90dd74388..30e7a4c10 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -509,18 +509,25 @@ and weak ecosystem. Where every developer would not be able to reliably trade th # Unresolved questions [unresolved-questions]: #unresolved-questions The unresolved questions, are really what type of metadata , is required for this spec? +- We decided that having metadata on chain is good, and that at later time we can have a spec for extra data if needed Can we represent events in this spec, which would improve the ecosystem quite a bit? If we represent events what should those events be? +- We decided events aren't here yet in the ecosystem, in the way we' like them to be so they are't apart of this standard Should we have a spec for TokenType? +- We decided TokkenType shouldn't be exposed for the public interface consumption. We will leave this description in the hands of the implementers Should we have a spec for offchain metadata? +- We decided no spec for offchain metadta yet but maybe in the future Does the current storage management scheme work for people? +- The current storage management scheme works for folks How freeform should this token be? Right now there is a notion of supply, which is not 100% guaranteed every token has or wants to track supply semantics. Not having supply makes everything more difficult and requires consumers of the contract to track minting and burning events. +- This point was taken to show that due to lack of proper events, and having the additional capability of storing this on chain, would +result in a reduction of complexity for consumers of the contract data. Approval Management is probably out of the scope of this solution. From 6a258f835eba18227231a17ce1136779b95f8bce Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 3 Sep 2021 15:00:42 -0700 Subject: [PATCH 010/108] fix: missing mt prefix from balance and suppky methods --- specs/Standards/MultiToken/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 30e7a4c10..95666c66c 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -327,18 +327,18 @@ pub trait MultiTokenCore { /// Get the balance of an an account given token_id. For fungible token returns back amount, for /// non fungible token it returns back constant 1. - fn balance_of(&self, owner_id: AccountId, token_id: TokenId) -> U128; + fn mt_balance_of(&self, owner_id: AccountId, token_id: TokenId) -> U128; /// Get the balances of an an account given token_ids. For fungible token returns back amount, for /// non fungible token it returns back constant 1. returns vector of balances corresponding to token_ids /// in a 1-1 mapping - fn balance_of_batch(&self, owner_id: AccountId, token_ids: Vec) -> Vec; + fn mt_balance_of_batch(&self, owner_id: AccountId, token_ids: Vec) -> Vec; /// Returns the total supply of the token in a decimal string representation given token_id. - fn total_supply(&self, token_id: TokenId) -> U128; + fn mt_total_supply(&self, token_id: TokenId) -> U128; // Returns the total supplies of the tokens given by token_ids in a decimal string representation. - fn total_supply_batch(&self, token_ids: Vec) -> Vec; + fn mt_total_supply_batch(&self, token_ids: Vec) -> Vec; } ``` ### Receiver Trait From 4af4107cf14c35938002d0bc09718470af5498b9 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 3 Sep 2021 15:02:05 -0700 Subject: [PATCH 011/108] chore: typo correction Co-authored-by: Mike Purvis --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 95666c66c..ab3164c78 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -515,7 +515,7 @@ Can we represent events in this spec, which would improve the ecosystem quite a - We decided events aren't here yet in the ecosystem, in the way we' like them to be so they are't apart of this standard Should we have a spec for TokenType? -- We decided TokkenType shouldn't be exposed for the public interface consumption. We will leave this description in the hands of the implementers +- We decided TokenType shouldn't be exposed for the public interface consumption. We will leave this description in the hands of the implementers Should we have a spec for offchain metadata? - We decided no spec for offchain metadta yet but maybe in the future From fa4cd9de09c6db245932cb3fafb03be569ad4080 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 3 Sep 2021 15:02:22 -0700 Subject: [PATCH 012/108] chore: typo correction Co-authored-by: Mike Purvis --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index ab3164c78..54680908f 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -518,7 +518,7 @@ Should we have a spec for TokenType? - We decided TokenType shouldn't be exposed for the public interface consumption. We will leave this description in the hands of the implementers Should we have a spec for offchain metadata? -- We decided no spec for offchain metadta yet but maybe in the future +- We decided no spec for offchain metadata yet but maybe in the future Does the current storage management scheme work for people? - The current storage management scheme works for folks From 4c821ae03c3d3bdc770c9f5aba1dee67c4ebf01d Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 3 Sep 2021 15:08:07 -0700 Subject: [PATCH 013/108] fix: this makes symbol optional to be more in alignment with the other metadata standards. Symbol required doesn't make sense for NFT like tokens, and there fore should be optional. This will also serve to help consumers subjectively understand the nature of the token. --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 54680908f..5d90be2b4 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -465,7 +465,7 @@ pub trait StorageManagement { pub struct MultiTokenMetadata { pub spec: String, // required, essentially a version like "mt-1.0.0" pub name: String, // required, ex. "Mosaics" - pub symbol: String, // required, ex. "MOSIAC" + pub symbol: Option, // ex. "MOSIAC" pub icon: Option, // Data URL pub base_uri: Option, // Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs // supports metadata_uri interface that interpolates {id} in the string From 3ea9de15440d4f20e395a4e2161bdf67a5d20d15 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 9 Sep 2021 10:15:13 -0700 Subject: [PATCH 014/108] fix: add verson information at the top --- specs/Standards/MultiToken/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 5d90be2b4..2eab561a3 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -1,6 +1,9 @@ - Proposal Name: Multi-Token-Standard - Start Date: 2021/07/24 - Issue(s): #245. + +Version `1.0.0` + # Summary [summary]: #summary From 85643b29061ed1c91cbdfb247c55d88fd4d43987 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 9 Sep 2021 10:16:16 -0700 Subject: [PATCH 015/108] fix: remove gnr8 example doesn't adhere to standard Co-authored-by: Mike Purvis --- specs/Standards/MultiToken/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 2eab561a3..3ef8ed995 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -21,7 +21,6 @@ Prior art: - This NEP derives some examples and format from: https://github.com/near/NEPs/pull/21 -- Example of an NFT Series: https://github.com/near-apps/gnr8 - Things from the NFT Discussions: https://github.com/near/NEPs/discussions/171 From 20c3ad6850a3a95bdc6f25856f90ac3e4e1a2eb9 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 00:24:21 -0700 Subject: [PATCH 016/108] chore: typo correction Co-authored-by: Mike Purvis --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 3ef8ed995..9aeaa1aba 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -13,7 +13,7 @@ A standard interface for a multi token standard that supports fungible, semi-fu [motivation]: #motivation Having a single contract represent both NFTs and FTs can greatly improve efficiency as demonstrated by Enjin Coin. The ability to make batch requests with multiple asset classes can reduce a many transactions transaciton to a single transaction to trade around both NFTs and FTs that are a part of same token contract. -Having this will also increase Near's ability to work interoperably with other chains. This will reduce the complexity required to represent these emerging asset classes. +Having this will also increase NEAR's ability to work interoperably with other chains. This will reduce the complexity required to represent these emerging asset classes. Prior art: From 36ada8c0b4f159a582bf45f04c9d8b582703a859 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 00:24:45 -0700 Subject: [PATCH 017/108] chore: consistency of layout Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 9aeaa1aba..f14a9f8f7 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -22,9 +22,9 @@ Prior art: - This NEP derives some examples and format from: https://github.com/near/NEPs/pull/21 -- Things from the NFT Discussions: https://github.com/near/NEPs/discussions/171 - -- Things from the NFT Discussions: https://gov.near.org/t/nft-standard-discussion/853 +- NFT Discussions: +https://github.com/near/NEPs/discussions/171 +https://gov.near.org/t/nft-standard-discussion/853 Discussions out of band: - https://gov.near.org/t/multi-token-standard-discussion/2917 From a41e5515f1321b045cd605807ce4c4aabac47c58 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 11:39:26 -0700 Subject: [PATCH 018/108] chore: lint typo Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index f14a9f8f7..dd3e7aa19 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -7,7 +7,7 @@ Version `1.0.0` # Summary [summary]: #summary -A standard interface for a multi token standard that supports fungible, semi-fungible, and tokens of any type, allowing for ownership, transfer, and batch transfer of tokens generally regardless of specific type. +A standard interface for a multi token standard that supports fungible, semi-fungible, and tokens of any type, allowing for ownership, transfer, and batch transfer of tokens generally regardless of specific type. # Motivation [motivation]: #motivation From 677cc7457d96b08e02c4dfa83daa1d447b4c98dc Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 11:40:40 -0700 Subject: [PATCH 019/108] chore: correct wording to make things more clear Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index dd3e7aa19..fec9bee09 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -11,7 +11,7 @@ A standard interface for a multi token standard that supports fungible, semi-fun # Motivation [motivation]: #motivation -Having a single contract represent both NFTs and FTs can greatly improve efficiency as demonstrated by Enjin Coin. The ability to make batch requests with multiple asset classes can reduce a many transactions transaciton to a single transaction to trade around both NFTs and FTs that are a part of same token contract. +Having a single contract represent both NFTs and FTs can greatly improve efficiency as demonstrated by Enjin Coin. The ability to make batch requests with multiple asset classes can allow operations that currently require _many_ transactions to be completed in a single transaction that can transfer both NFTs and FTs that are a part of same token contract. Having this will also increase NEAR's ability to work interoperably with other chains. This will reduce the complexity required to represent these emerging asset classes. From f5d7631d6daf40a0ddf7c9b5597c4e2809a04f50 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 11:44:57 -0700 Subject: [PATCH 020/108] chore: tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index fec9bee09..d89f36d43 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -55,7 +55,7 @@ There are a few concepts in the scenarios above: #### Simple transfer -Alice wants to send 5 gold tokens to Bob. +Alice wants to send 5 `gold` tokens to Bob. Let's assume the following: - The `gold` token is represented by the `games` with token_id `g133`. From caee79b6243d3a7d7dadff1021fbf191f1b44a54 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 11:46:32 -0700 Subject: [PATCH 021/108] chore: tidy and clarify Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index d89f36d43..e7443436b 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -62,7 +62,7 @@ Let's assume the following: - Alice's account is `alice`. - Bob's account is `bob`. - The precision for `gold` on the games contract is `10^8`. -- The 5 tokens is `5 * 10^8` or as a number is `500000000`. +- The `amount` to represent 5 `gold` tokens is `5 * 10^8`, or as a number is `500000000`. High-level explanation: From 96f61887b668053200cf0093d8dc11be4438e51b Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 11:47:53 -0700 Subject: [PATCH 022/108] chore: typo correction and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index e7443436b..e11f7c03e 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -74,7 +74,7 @@ Technical calls: #### Simple batch transfer -Alice wants to send and a 1 unique(nft) gemstone, 5 gold and 10 silver tokens to Bob. +Alice wants to send 1 unique (non-fungible) `gemstone`, 5 `gold` (fungible) and 10 `silver` (fungible) tokens to Bob. Let's assume the following: - The unique nft `gem` token is represented by `games` with token_id From 08f0adcb16d2a9a1a4ce2ddb37ccf3aa88392b97 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 11:53:15 -0700 Subject: [PATCH 023/108] chore: lint correction Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index e11f7c03e..a7c788877 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -218,7 +218,7 @@ pub trait MultiTokenCore { /// contract at the method `mt_resolve_transfer`. /// /// You can think of this as being similar to attaching tokens to a - /// function call. It allows you to attach any Fungible or Non Fungible Token in a call to a + /// function call. It allows you to attach any FungibleToken or NonFungibleToken in a call to a /// receiver contract. /// /// Requirements: From 3519b34f43b670256923efc0663d47a76b317016 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 11:53:49 -0700 Subject: [PATCH 024/108] chore: tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index a7c788877..c488379c6 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -191,7 +191,7 @@ pub trait MultiTokenCore { /// Basic token transfer. Transfer a token or tokens given a token_id. The token id can correspond to /// either a NonFungibleToken or Fungible Token this is differeniated by the implementation. /// - /// Requirements + /// Requirements: /// * Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes /// * Contract MUST panic if called by someone other than token owner or, /// * If using Approval Management, contract MUST nullify approved accounts on From 567dfe64a10bf4ae153fd2ebe68806db238b4f6f Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 11:56:22 -0700 Subject: [PATCH 025/108] chore: typo correction Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index c488379c6..5f22a0345 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -453,8 +453,8 @@ pub trait StorageManagement { /// the contract doesn't support force unregistration. /// MUST require exactly 1 yoctoNEAR attached balance to prevent restricted function-call access-key call /// (UX wallet security) - /// Returns `true` iff the account was unregistered. - /// Returns `false` iff account was not registered before. + /// Returns `true` if the account was successfully unregistered by this call. + /// Returns `false` if account was already unregistered. fn storage_unregister(&mut self, token_ids:Vec, force: Option) -> Vec; fn storage_balance_bounds(&self, token_id:Vec, account_id: Option) -> StorageBalanceBounds; From d2501f2a7baa39d6194f579ead20796a881e5f15 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 11:57:38 -0700 Subject: [PATCH 026/108] chore: clarify Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 5f22a0345..2c12feffe 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -493,7 +493,7 @@ pub trait MultiTokenMetadataProvider { ``` # Drawbacks [drawbacks]: #drawbacks -Doing this adds another spec and codebase to the standards. It could be seen that we could leave this to developers to implement custom solutions and have them create a token that just uses FT and NFT together. There is some additional complexity +Doing this adds another spec and codebase to the standards. It could be seen that we could leave this to developers to implement custom solutions and have them create a contract that implements both `NEP-141` and `NEP-171` methods together. There is some additional complexity in ux, when considering batch size request and gas limitations, that might trip some developers up. # Rationale and alternatives From 8dafe7c05a997aca6b0f5cab18501dc79497003e Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:01:41 -0700 Subject: [PATCH 027/108] chore: lint tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 2c12feffe..c9598398d 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -58,7 +58,7 @@ There are a few concepts in the scenarios above: Alice wants to send 5 `gold` tokens to Bob. Let's assume the following: -- The `gold` token is represented by the `games` with token_id `g133`. +- The `gold` token is defined in the `games.near` contract with `token_id` of `g133`. - Alice's account is `alice`. - Bob's account is `bob`. - The precision for `gold` on the games contract is `10^8`. From 5be2439bc03591fe12a714e5dbcb9998f5618554 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:52:30 -0700 Subject: [PATCH 028/108] fix: refactor to full spec naming Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index c9598398d..a76d422b3 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -59,8 +59,8 @@ Alice wants to send 5 `gold` tokens to Bob. Let's assume the following: - The `gold` token is defined in the `games.near` contract with `token_id` of `g133`. -- Alice's account is `alice`. -- Bob's account is `bob`. +- Alice's account is `alice.near`. +- Bob's account is `bob.near`. - The precision for `gold` on the games contract is `10^8`. - The `amount` to represent 5 `gold` tokens is `5 * 10^8`, or as a number is `500000000`. From 6697f42675ddb3844a3fc528bf423babd7998de5 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:53:12 -0700 Subject: [PATCH 029/108] fix: refactor to full spec naming Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index a76d422b3..810f4852d 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -61,7 +61,7 @@ Let's assume the following: - The `gold` token is defined in the `games.near` contract with `token_id` of `g133`. - Alice's account is `alice.near`. - Bob's account is `bob.near`. -- The precision for `gold` on the games contract is `10^8`. +- The precision for `gold` on the `games.near` contract is `10^8`. - The `amount` to represent 5 `gold` tokens is `5 * 10^8`, or as a number is `500000000`. High-level explanation: From eb0b7da986cf8baca9f0fd6f84bcffc140a0d5ab Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:53:43 -0700 Subject: [PATCH 030/108] chore: refactor to full spec naming and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 810f4852d..c283ae86b 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -66,7 +66,7 @@ Let's assume the following: High-level explanation: -Alice needs to issue one transaction to `games` contract to transfer 5 tokens (multiplied by precision) to Bob. +Alice needs to issue one transaction to the `games.near` contract to transfer 5 `gold` tokens (multiplied by the precision defined in `gold` token metadata) to Bob. Technical calls: From c090e4656ab64ae8fc9b667fd376cf1be8b758b7 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:54:10 -0700 Subject: [PATCH 031/108] fix: refactor to full spec naming Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index c283ae86b..37328c235 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -70,7 +70,7 @@ Alice needs to issue one transaction to the `games.near` contract to transfer 5 Technical calls: -1. `alice` calls `games::mt_transfer({"receiver_id": "bob", "amount": "500000000", "token_id": "g133", "memo": "for my dinner"})`. +1. `alice.near` calls `games.near::mt_transfer({"receiver_id": "bob.near", "amount": "500000000", "token_id": "g133", "memo": "for my dinner"})`. #### Simple batch transfer From a2ce8013cc03245980afb6e47255880e39bbdc47 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:54:36 -0700 Subject: [PATCH 032/108] fix: refactor to full spec naming and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 37328c235..c6ab6c781 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -77,17 +77,17 @@ Technical calls: Alice wants to send 1 unique (non-fungible) `gemstone`, 5 `gold` (fungible) and 10 `silver` (fungible) tokens to Bob. Let's assume the following: -- The unique nft `gem` token is represented by `games` with token_id +- The unique (non-fungible) `gem` token is defined in the `games.near` contract with `token_id` `uu2` -- The `gold` token is represented by the `games` with token_id `g133`. -- The `silver` token is represented by the `games` with token_id `s133`. -- Alice's account is `alice`. -- Bob's account is `bob`. -- The precision for `gold` on the games contract is `10^8`. -- The precision for `silver` on the games contract is also `10^8`. -- The 5 gold tokens is `5 * 10^8` or as a number is `500000000`. -- The 10 silver tokens is `10 * 10^8` or as a number is `1000000000`. -- The 1 gem token is `1` or as a number is `1` +- The `gold` token is defined in the `games.near` contract with `token_id` `g133`. +- The `silver` token is defined in the `games.near` contract with `token_id` `s133`. +- Alice's account is `alice.near`. +- Bob's account is `bob.near`. +- The precision for `gold` on the `games.near` contract is `10^8`. +- The precision for `silver` on the `games.near` contract is also `10^8`. +- The `amount` to represent 5 `gold` tokens is `5 * 10^8` or as a number is `500000000`. +- The `amount` to represent 10 `silver` tokens is `10 * 10^8` or as a number is `1000000000`. +- The `amount` to represent 1 `gem` token is `1` or as a number is `1` High-level explanation: From 90d5605e4627022631d20234e492415edce7dcc7 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:55:18 -0700 Subject: [PATCH 033/108] chore: refactor to full spec naming and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index c6ab6c781..e3bee5c53 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -91,11 +91,11 @@ Let's assume the following: High-level explanation: -Alice needs to issue one transaction to `games` contract to transfer 5 gold tokens and 10 silver tokens (multiplied by precision) and 1 gem to Bob. +Alice needs to issue one transaction to `games.near` contract to transfer 5 `gold` tokens and 10 `silver` tokens (multiplied by precision) and 1 `gem` to Bob. Technical calls: -1. `alice` calls `games::mt_transfer_batch({"receiver_id": "bob", "amounts": ["500000000", "1000000000", "1"], "token_ids": ["g133", "s133", "uu2"], "memo": "well done"})`. +1. `alice.near` calls `games.near::mt_transfer_batch({"receiver_id": "bob.near", "amounts": ["500000000", "1000000000", "1"], "token_ids": ["g133", "s133", "uu2"], "memo": "well done"})`. From ddda89316aa534f8fa65f96b5573743ea446082a Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:55:46 -0700 Subject: [PATCH 034/108] chore: refactor to full spec naming Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index e3bee5c53..a635bfb61 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -113,9 +113,9 @@ Let's assume the following: High-level explanation: -Alice needs to issue a single transaction to `games` that will internally issue a cross contract call to `compound`. +Alice needs to issue a single transaction to `games.near` that will internally issue a cross contract call to `compound.near`. -The initial transaction to `games` is made with `compound` as the receiver of a set token_ids and amounts from `alice`. +The initial transaction to `games.near` is made with `compound.near` as the receiver of a set token_ids and amounts from `alice.near`. This call then waits on a response from `compound`. If `compound` responds with failure, the tx is aborted. From 9ae02d71186a8f3420ca54f0b23aac8a3ec2fe26 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:56:11 -0700 Subject: [PATCH 035/108] chore: refactor to full spec naming and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index a635bfb61..7cb4846c1 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -104,12 +104,12 @@ Technical calls: Alice wants to deposit `gold` tokens to a compound interest contract to earn some rewards. Let's assume the following: -- The `gold` token is represented by the `games` contract with token_id `g133` . -- Alice's account is `alice`. -- The compound interest contract is `compound`. +- The `gold` token is represented by the `games.near` contract with `token_id` `g133` . +- Alice's account is `alice.near`. +- The compound interest contract is `compound.near`. - The precision on `gold` token is `10^18`. -- The 1000 tokens is `1000 * 10^18` or as a number is `1000000000000000000000`. -- The compound contract can work with many different token contracts and types. +- The `amount` to represent 1000 tokens is `1000 * 10^18` or as a number is `1000000000000000000000`. +- The `compound.near` contract can work with many different token contracts and types. High-level explanation: From 00bb5cf6c7c4b56e45955797e3460d4c88208899 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:56:48 -0700 Subject: [PATCH 036/108] chore: refactor to full spec naming Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 7cb4846c1..3944a2ae3 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -117,13 +117,13 @@ Alice needs to issue a single transaction to `games.near` that will internally i The initial transaction to `games.near` is made with `compound.near` as the receiver of a set token_ids and amounts from `alice.near`. -This call then waits on a response from `compound`. If `compound` responds with failure, the tx is aborted. +This call then waits on a response from `compound.near`. If `compound.near` responds with failure, the tx is aborted. -Otherwise `games` contract accepts the results and resolves the promise completing the transaction. +Otherwise `games.near` contract accepts the results and resolves the promise completing the transaction. -- If transfer succeeded, `compound` can increase local ownership for `alice` to 1000 for `gold` token_id `g133` +- If transfer succeeded, `compound.near` can increase local ownership for `alice.near` to 1000 for `gold` , whose `token_id` is `g133` -- If transfer fails, `compound` doesn't need to do anything in current example, but maybe can notify `alice` of unsuccessful transfer. +- If transfer fails, `compound.near` doesn't need to do anything in current example, but maybe can notify `alice.near` of unsuccessful transfer. Technical calls: 1. `alice` calls `games::mt_transfer_call({"receiver_id": "compound", amount: "1000000000000000000000", "token_id": "g133", msg: "interest-building"})`. From 65cbea1a2c1618c78f6cdb37819044c702119d0f Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:57:13 -0700 Subject: [PATCH 037/108] chore: refactor to full spec naming and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 3944a2ae3..c2a3f94ac 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -127,7 +127,7 @@ Otherwise `games.near` contract accepts the results and resolves the promise com Technical calls: 1. `alice` calls `games::mt_transfer_call({"receiver_id": "compound", amount: "1000000000000000000000", "token_id": "g133", msg: "interest-building"})`. - During the `mt_transfer_call` call, `compound` does the following: + During the `mt_transfer_call` call, `compound.near` does the following: fn mt_on_transfer( &mut self, sender_id: AccountId, From 95b4b017ebd70ee12a056bbcf03843d2b7609bdb Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:57:50 -0700 Subject: [PATCH 038/108] chore: refactor to full spec naming and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index c2a3f94ac..b67ddda86 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -126,7 +126,7 @@ Otherwise `games.near` contract accepts the results and resolves the promise com - If transfer fails, `compound.near` doesn't need to do anything in current example, but maybe can notify `alice.near` of unsuccessful transfer. Technical calls: -1. `alice` calls `games::mt_transfer_call({"receiver_id": "compound", amount: "1000000000000000000000", "token_id": "g133", msg: "interest-building"})`. +1. `alice.near` calls `games.near::mt_transfer_call({"receiver_id": "compound.near", amount: "1000000000000000000000", "token_id": "g133", msg: "interest-building"})`. During the `mt_transfer_call` call, `compound.near` does the following: fn mt_on_transfer( &mut self, From a9d2afce20c58eac4726643f4a0d3ea1bf4d2ce9 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:58:17 -0700 Subject: [PATCH 039/108] chore: refactor to full spec naming and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index b67ddda86..9a9795bbb 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -141,18 +141,18 @@ Technical calls: #### Batch Token deposit to a contract -Alice wants to deposit `silver` and `gold` tokens and the nft `gem` to a compound interest contract to earn some rewards. +Alice wants to deposit `silver` (fungible) and `gold` (fungible) tokens and the unique `gem` (non-fungible) to a compound interest contract to earn some rewards. Let's assume the following: -- The `gold` token is represented by the `games` contract with token_id `g133` . -- The `silver` token is represented by the `games` contract with token_id `s133` . -- The `gem` unique only one nft token is represented by the `games` contract with token_id `uu2` . -- Alice's account is `alice`. -- The compound interest contract is `compound`. +- The `gold` token is represented by the `games.near` contract with `token_id` of `g133` . +- The `silver` token is represented by the `games.near` contract with `token_id` of `s133` . +- The `gem` unique only one nft token is represented by the `games.near` contract with `token_id` of `uu2` . +- Alice's account is `alice.near`. +- The compound interest contract is `compound.near`. - The precision on `gold` token is `10^18`. - The precision on `silver` token is `10^18`. -- The 1000 tokens is `1000 * 10^18` or as a number is `1000000000000000000000`. -- The compound contract can work with many different token contracts and types. +- The `amount` used to represent 1000 tokens is `1000 * 10^18` or as a number is `1000000000000000000000`. +- The `compound.near` contract can work with many different token contracts and types. High-level explanation: From 97f29cebe9b2bb0b9b5a50f80ff8d66af7a0ac04 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:59:00 -0700 Subject: [PATCH 040/108] chore: refactor to full spec naming and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 9a9795bbb..cc5bd671d 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -136,8 +136,8 @@ Technical calls: msg: String, ) -> PromiseOrValue>; } - 1. calls `compound::mt_on_transfer({"sender_id": "alice", "token_ids":["g133"], "amounts": ["1000000000000000000000"], msg: "interest-building"})`. - 2. `compound` resolves the request/fails and `games` contract handles response from the promise with `games::mt_resolve_transfer` returning refunded amount if there is any or handling follow up from the result of compound cross contract call + 1. calls `compound::mt_on_transfer({"sender_id": "alice.near", "token_ids":["g133"], "amounts": ["1000000000000000000000"], msg: "interest-building"})`. + 2. `compound.near` resolves the request/fails and `games.near` contract handles the result of the promise, with `games.near::mt_resolve_transfer()` returning refunded amount if there is any or handling follow up from the result of compound cross contract call #### Batch Token deposit to a contract From 15d87ff82b7b1c7be684f8c63e3a83be694f1536 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 12:59:39 -0700 Subject: [PATCH 041/108] chore: refactor to full spec naming and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index cc5bd671d..326e986ca 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -156,17 +156,17 @@ Let's assume the following: High-level explanation: -Alice needs to issue a single transaction to `games` that will internally issue a cross contract call to `compound`. +Alice needs to issue a single transaction to `games.near` that will internally issue a cross contract call to `compound.near`. -The initial transaction to `games` is made with `compound` as the receiver of a set token_ids and amounts from `alice`. +The initial transaction to `games.near` is made with `compound.near` as the receiver of a set token_ids and amounts from `alice`. -This call then waits on a response from `compound`. If `compound` responds with failure, the tx is aborted. +This call then waits on a response from `compound.near`. If `compound.near` responds with failure, the tx is aborted. -Otherwise `games` contract accepts the results and resolves the promise completing the transaction. +Otherwise `games.near` contract accepts the results and resolves the promise completing the transaction. -- If transfer succeeded, `compound` can increase local ownership for `alice` to 1000 for `gold` token_id `g133` +- If transfer succeeded, `compound.near` can increase local ownership for `alice.near` to 1000 for `gold` with token_id `g133` -- If transfer fails, `compound` doesn't need to do anything in current example, but maybe can notify `alice` of unsuccessful transfer. +- If transfer fails, `compound.near` doesn't need to do anything in current example, but maybe can notify `alice.near` of unsuccessful transfer. Technical calls: 1. `alice` calls `games::mt_transfer_batch_call({"receiver_id": "compound", amounts: ["1000000000000000000000","1000000000000000000000", "1"], "token_ids": ["g133","s133","uu2"], msg: "interest-building"})`. From 7061c207ddca8cb4892e12642cbb78b62cd6c3d8 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 13:00:04 -0700 Subject: [PATCH 042/108] chore: refactor to full spec naming and tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 326e986ca..f44e75c4a 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -169,8 +169,8 @@ Otherwise `games.near` contract accepts the results and resolves the promise com - If transfer fails, `compound.near` doesn't need to do anything in current example, but maybe can notify `alice.near` of unsuccessful transfer. Technical calls: -1. `alice` calls `games::mt_transfer_batch_call({"receiver_id": "compound", amounts: ["1000000000000000000000","1000000000000000000000", "1"], "token_ids": ["g133","s133","uu2"], msg: "interest-building"})`. - During the `mt_transfer_call` call, `compound` does the following: +1. `alice.near` calls `games.near::mt_transfer_batch_call({"receiver_id": "compound.near", amounts: ["1000000000000000000000","1000000000000000000000", "1"], "token_ids": ["g133","s133","uu2"], msg: "interest-building"})`. + During the `mt_transfer_call` call, `compound.near` does the following: fn mt_on_transfer( &mut self, sender_id: AccountId, @@ -179,8 +179,8 @@ Technical calls: msg: String, ) -> PromiseOrValue>; } - 1. calls `compound::mt_on_transfer({"sender_id": "alice", amounts: ["1000000000000000000000","1000000000000000000000", "1"], "token_ids": ["g133","s133","uu2"], msg: "interest-building"})` - 2. `compound` resolves the request/fails and `games` contract handles response from the promise with `games::mt_resolve_transfer` returning refunded amount if there is any or handling follow up from the result of compound cross contract call + 1. calls `compound.near::mt_on_transfer({"sender_id": "alice.near", amounts: ["1000000000000000000000","1000000000000000000000", "1"], "token_ids": ["g133","s133","uu2"], msg: "interest-building"})` + 2. `compound.near` resolves the request/fails and `games.near` contract handles response from the promise with `games.near::mt_resolve_transfer` returning refunded amount if there is any or handling follow up from the result of compound cross contract call # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From 7c5210a8e942d88ad1591bb18bdd0c5d40d67468 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 13:01:22 -0700 Subject: [PATCH 043/108] chore: tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index f44e75c4a..2c2229fa6 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -189,7 +189,7 @@ WIP implementation: https://github.com/shipsgold/multi-token-standard-impl/tree/ ``` pub trait MultiTokenCore { /// Basic token transfer. Transfer a token or tokens given a token_id. The token id can correspond to - /// either a NonFungibleToken or Fungible Token this is differeniated by the implementation. + /// either a NonFungibleToken or FungibleToken - this is differentiated by the implementation. /// /// Requirements: /// * Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes From 913f58ce4fc3d121666738cc41407a204e21b88e Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 10 Sep 2021 13:01:50 -0700 Subject: [PATCH 044/108] chore: clarification Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 2c2229fa6..54c5bc497 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -217,7 +217,7 @@ pub trait MultiTokenCore { /// workflow will end in a success execution outcome to the callback on the MultiToken /// contract at the method `mt_resolve_transfer`. /// - /// You can think of this as being similar to attaching tokens to a + /// You can think of this as being similar to attaching NEAR tokens as a `deposit` to a /// function call. It allows you to attach any FungibleToken or NonFungibleToken in a call to a /// receiver contract. /// From 133381a78c787d4d30862d555019b298a88a45e3 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Mon, 13 Sep 2021 10:01:39 -0700 Subject: [PATCH 045/108] chore: typo correction Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 54c5bc497..0d9b02d53 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -287,7 +287,7 @@ pub trait MultiTokenCore { /// workflow will end in a success execution outcome to the callback on the MultiToken /// contract at the method `mt_resolve_batch_transfer`. /// - /// You can think of this as being similar to attaching tokens to a + /// You can think of this as being similar to attaching NEAR tokens as a `deposit` to a /// function call. It allows you to attach any Fungible or Non Fungible Token in a call to a /// receiver contract. /// From 9a534999a04913606079be847ab6f8f851f7be8d Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Mon, 13 Sep 2021 10:26:28 -0700 Subject: [PATCH 046/108] feat: add an approval management standard proposal for discussion --- .../MultiToken/ApprovalManagement.md | 645 ++++++++++++++++++ 1 file changed, 645 insertions(+) create mode 100644 specs/Standards/MultiToken/ApprovalManagement.md diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md new file mode 100644 index 000000000..8c30ad60c --- /dev/null +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -0,0 +1,645 @@ +# Multi Token Standard Approval Management + +Version `1.0.0` + +## Summary + +A system for allowing a set of users or contracts to transfer specific tokens on behalf of an owner. Similar to approval management systems in standards like [ERC-721] and [ERC-1155]. + + [ERC-721]: https://eips.ethereum.org/EIPS/eip-721 + [ERC-1155]: https://eips.ethereum.org/EIPS/eip-1155 +## Motivation + +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. + +NEAR's [core Multi Token standard](README.md) 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 Managament 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] + +## Example Scenarios + +Let's consider some examples. Our cast of characters & apps: + +* Alice: has account `alice` with no contract deployed to it +* Bob: has account `bob` with no contract deployed to it +* MT: a contract with account `mt`, implementing only the [Multi Token Standard](README.md) with this Approval Management extension +* Market: a contract with account `market` which sells tokens from `mt` as well as other token contracts +* Bazaar: similar to Market, but implemented differently (spoiler alert: has no `mt_on_approve` function!), has account `bazaar` + +Alice and Bob are already [registered](README.md) with MT, Market, and Bazaar, and Alice owns a token on the MT contract with ID=`"1"` and a fungible style token with ID =`"2"` and AMOUNT =`"100"`. + +Let's examine the technical calls through the following scenarios: + +1. [Simple approval](#1-simple-approval): Alice approves Bob to transfer her token. +2. [Simple approval all](#2-simple-approval-all): Alice approves Bob to transfer all her tokens and future tokens she may mint. +3. [Approval with cross-contract call (XCC)](#3-approval-with-cross-contract-call): Alice approves Market to transfer one of her tokens and passes `msg` so that NFT will call `mt_on_approve` on Market's contract. +4. [Approval with XCC, edge case](#4-approval-with-cross-contract-call-edge-case): Alice approves Bazaar and passes `msg` again, but what's this? Bazaar doesn't implement `mt_on_approve`, so Alice sees an error in the transaction result. Not to worry, though, she checks `mt_is_approved` and sees that she did successfully approve Bazaar, despite the error. +5. [Approval IDs](#5-approval-ids): Bob buys Alice's token via Market. +6. [Approval IDs, edge case](#6-approval-ids-edge-case): Bob transfers same token back to Alice, Alice re-approves Market & Bazaar. Bazaar has an outdated cache. Bob tries to buy from Bazaar at the old price. +7. [Revoke one](#7-revoke-one): Alice revokes Market's approval for this token. +8. [Revoke all](#8-revoke-all): Alice revokes all approval for this token. + +### 1. Simple Approval + +Alice approves Bob to transfer her token. + +**High-level explanation** + +1. Alice approves Bob +2. Alice queries the token to verify +3. Alice verifies a different way + +**Technical calls** + +1. Alice calls `mt::mt_approve({ "token_ids": ["1","2"], "account_id": "bob" })`. She attaches 1 yoctoⓃ, (.000000000000000000000001Ⓝ). Using [NEAR CLI](https://docs.near.org/docs/tools/near-cli) to make this call, the command would be: + + near call mt mt_approve \ + '{ "token_id": "1", "account_id": "bob" }' \ + --accountId alice --amount .000000000000000000000001 + + The response: + + '' + +2. Alice calls view method `mt_approvals`: + + near view mt mt_approvals \ + '{ "token_id": ["1", "2"] }' + + The response: + + { + "ids": ["1", "2"] + "owner_id": "alice.near", + "approvals": [{ + "bob": 1, + }, + { + "bob": 2, + }] + } + +3. Alice calls view method `mt_is_approved`: + + near view mt mt_is_approved \ + '{ "token_ids": ["1", "2"], "approved_account_id": "bob" }' + + The response: + + true + +### 2. Simple Approval All + +Alice approves Bob for all token_ids to transfer her tokens. + +**High-level explanation** + +1. Alice approves Bob +2. Alice queries the token to verify +3. Alice verifies a different way + +**Technical calls** + +1. Alice calls `mt::mt_approve_all({"account_id": "bob" })`. She attaches 1 yoctoⓃ, (.000000000000000000000001Ⓝ). Using [NEAR CLI](https://docs.near.org/docs/tools/near-cli) to make this call, the command would be: + + near call mt mt_approve_all \ + '{"account_id": "bob"}' \ + --accountId alice --amount .000000000000000000000001 + + The response: + + '' + +2. Alice calls view method `mt_approvals`: + + near view mt mt_approvals \ + '{ "token_id": ["1", "2"] }' + + The response: + + { + "ids": ["1", "2"] + "owner_id": "alice.near", + "approvals": [{ + "bob": 1, + }, + { + "bob": 2, + }] + } + +3. Alice calls view method `mt_is_approved`: + + near view mt mt_is_approved \ + '{ "token_ids": ["1", "2"], "approved_account_id": "bob" }' + + The response: + + true + +### 3. Approval with cross-contract call + +Alice approves Market to transfer one of her tokens and passes `msg` so that MT will call `mt_on_approve` on Market's contract. She probably does this via Market's frontend app which would know how to construct `msg` in a useful way. + +**High-level explanation** + +1. Alice calls `mt_approve` to approve `market` to transfer her token, and passes a `msg` +2. Since `msg` is included, `mt` will schedule a cross-contract call to `market` +3. Market can do whatever it wants with this info, such as listing the token for sale at a given price. The result of this operation is returned as the promise outcome to the original `mt_approve` call. + +**Technical calls** + +1. Using near-cli: + + near call mt mt_approve '{ + "token_id": ["1","2"], + "account_id": "market", + "msg": "{\"action\": \"list\", \"price\": \"100\", \"token\": \"nDAI\" }" + }' --accountId alice --amount .000000000000000000000001 + + At this point, near-cli will hang until the cross-contract call chain fully resolves, which would also be true if Alice used a Market frontend using [near-api-js](https://docs.near.org/docs/develop/front-end/near-api-js). Alice's part is done, though. The rest happens behind the scenes. + +2. `mt` schedules a call to `mt_on_approve` on `market`. Using near-cli notation for easy cross-reference with the above, this would look like: + + near call market mt_on_approve '{ + "token_id": ["1","2"], + "owner_id": "alice", + "approval_id": 2, + "msg": "{\"action\": \"list\", \"price\": [\"100\",\"50\"], \"token\": \"nDAI\" }" + }' --accountId mt + +3. `market` now knows that it can sell Alice's token for 100 [nDAI](https://explorer.mainnet.near.org/accounts/6b175474e89094c44da98b954eedeac495271d0f.factory.bridge.near), and that when it transfers it to a buyer using `mt_batch_transfer`, it can pass along the given `approval_id` to ensure that Alice hasn't changed her mind. It can schedule any further cross-contract calls it wants, and if it returns these promises correctly, Alice's initial near-cli call will resolve with the outcome from the final step in the chain. If Alice actually made this call from a Market frontend, the frontend can use this return value for something useful. + + + +### 3. Approval all with cross-contract call + +Alice approves Market to transfer some of her tokens and passes `msg` so that MT will call `mt_on_approve_all` on Market's contract. She probably does this via Market's frontend app which would know how to construct `msg` in a useful way. + +**High-level explanation** + +1. Alice calls `mt_approve_all` to approve `market` to transfer her tokens, and passes a `msg` +2. Since `msg` is included, `mt` will schedule a cross-contract call to `market` +3. Market can do whatever it wants with this info, such as listing the token for sale at a given price. The result of this operation is returned as the promise outcome to the original `mt_approve_all` call. This will allow the +market to transfer the listing whenver necessary. In addition to this any future listings as well that this owner owns. In the case of a game +new items in the collection can be listed for sale at no additional +approval storage cost, within the contract. + +**Technical calls** + +1. Using near-cli: + + near call mt mt_approve_all '{ + "account_id": "market", + "msg": "{\"action\": \"approve"}" + }' --accountId alice --amount .000000000000000000000001 + + At this point, near-cli will hang until the cross-contract call chain fully resolves, which would also be true if Alice used a Market frontend using [near-api-js](https://docs.near.org/docs/develop/front-end/near-api-js). Alice's part is done, though. The rest happens behind the scenes. + +2. `mt` schedules a call to `mt_on_approve` on `market`. Using near-cli notation for easy cross-reference with the above, this would look like: + + near call market mt_on_approve_all '{ + "owner_id": "alice", + "approval_id": 2, + "msg": "{\"action\": \"approve\"}" + }' --accountId mt + +3. `market` now knows that it can approve selling or using alice's token. It also knows that when alice sets a price and that when it transfers it to a buyer using `mt_batch_transfer`, it can pass along the given `approval_id` to ensure that Alice hasn't changed her mind. It can schedule any further cross-contract calls it wants, and if it returns these promises correctly, Alice's initial near-cli call will resolve with the outcome from the final step in the chain. If Alice actually made this call from a Market frontend, the frontend can use this return value for something useful. + +### 3. Approval with cross-contract call, edge case + +Alice approves Bazaar and passes `msg` again. Maybe she actually does this via near-cli, rather than using Bazaar's frontend, because what's this? Bazaar doesn't implement `mt_on_approve`, so Alice sees an error in the transaction result. + +Not to worry, though, she checks `mt_is_approved` and sees that she did successfully approve Bazaar, despite the error. She will have to find a new way to list her token for sale in Bazaar, rather than using the same `msg` shortcut that worked for Market. + +**High-level explanation** + +1. Alice calls `mt_approve` to approve `bazaar` to transfer her token, and passes a `msg`. +2. Since `msg` is included, `mt` will schedule a cross-contract call to `bazaar`. +3. Bazaar doesn't implement `mt_on_approve`, so this call results in an error. The approval still worked, but Alice sees an error in her near-cli output. +4. Alice checks if `bazaar` is approved, and sees that it is, despite the error. + +**Technical calls** + +1. Using near-cli: + + near call mt mt_approve '{ + "token_id": "1", + "account_id": "bazaar", + "msg": "{\"action\": \"list\", \"price\": \"100\", \"token\": \"nDAI\" }" + }' --accountId alice --amount .000000000000000000000001 + +2. `mt` schedules a call to `mt_on_approve` on `market`. Using near-cli notation for easy cross-reference with the above, this would look like: + + near call bazaar mt_on_approve '{ + "token_id": ["1","2"], + "owner_id": "alice", + "approval_id": 3, + "msg": "{\"action\": \"list\", \"price\": \"100\", \"token\": \"nDAI\" }" + }' --accountId mt + +3. 💥 `bazaar` doesn't implement this method, so the call results in an error. Alice sees this error in the output from near-cli. + +4. Alice checks if the approval itself worked, despite the error on the cross-contract call: + + near view mt mt_is_approved \ + '{ "token_id": "1", "approved_account_id": "bazaar" }' + + The response: + + true + +### 3. Approval all with cross-contract call, edge case + +Alice approves Bazaar and passes `msg` again. Maybe she actually does this via near-cli, rather than using Bazaar's frontend, because what's this? Bazaar doesn't implement `mt_on_approve_all`, so Alice sees an error in the transaction result. + +Not to worry, though, she checks `mt_is_approved` and sees that she did successfully approve Bazaar, despite the error. She will have to find a new way to list her token for sale in Bazaar, rather than using the same `msg` shortcut that worked for Market. + +**High-level explanation** + +1. Alice calls `mt_approve_all` to approve `bazaar` to transfer her token, and passes a `msg`. +2. Since `msg` is included, `mt` will schedule a cross-contract call to `bazaar`. +3. Bazaar doesn't implement `mt_on_approve_all`, so this call results in an error. The approval still worked, but Alice sees an error in her near-cli output. +4. Alice checks if `bazaar` is approved, and sees that it is, despite the error. + +**Technical calls** + +1. Using near-cli: + + near call mt mt_approve_all '{ + "account_id": "bazaar", + "msg": "{\"action\": \"approve\"}" + }' --accountId alice --amount .000000000000000000000001 + +2. `mt` schedules a call to `mt_on_approve_all` on `market`. Using near-cli notation for easy cross-reference with the above, this would look like: + + near call bazaar mt_on_approve_all '{ + "owner_id": "alice", + "approval_id": 3, + "msg": "{\"action\": \"approve\" }" + }' --accountId mt + +3. 💥 `bazaar` doesn't implement this method, so the call results in an error. Alice sees this error in the output from near-cli. + +4. Alice checks if the approval itself worked, despite the error on the cross-contract call: + + near view mt mt_is_approved \ + '{ "token_id": "1", "approved_account_id": "bazaar" }' + + The response: + + true + + +### 4. Approval IDs + +Bob buys Alice's token via Market. Bob probably does this via Market's frontend, which will probably initiate the transfer via a call to `ft_transfer_call` on the nDAI contract to transfer 100 nDAI to `market`. Like the NFT standard's "transfer and call" function, [Fungible Token](../FungibleToken/Core.md)'s `ft_transfer_call` takes a `msg` which `market` can use to pass along information it will need to pay Alice and actually transfer the MT. The actual transfer of the MT is the only part we care about here. + +**High-level explanation** + +1. Bob signs some transaction which results in the `market` contract calling `mt_transfer` on the `mt` contract, as described above. To be trustworthy and pass security audits, `market` needs to pass along `approval_id` so that it knows it has up-to-date information. + +**Technical calls** + +Using near-cli notation for consistency: + + near call mt mt_transfer '{ + "receiver_id": "bob", + "token_id": "1", + "approval_id": 2, + }' --accountId market --amount .000000000000000000000001 + +### 5. Approval IDs, edge case +Bob transfers same token back to Alice, Alice re-approves Market & Bazaar, listing her token at a higher price than before. Bazaar is somehow unaware of these changes, and still stores `approval_id: 3` internally along with Alice's old price. Bob tries to buy from Bazaar at the old price. Like the previous example, this probably starts with a call to a different contract, which eventually results in a call to `mt_transfer` on `bazaar`. Let's consider a possible scenario from that point. + +**High-level explanation** + +Bob signs some transaction which results in the `bazaar` contract calling `mt_transfer` on the `mt` contract, as described above. To be trustworthy and pass security audits, `bazaar` needs to pass along `approval_id` so that it knows it has up-to-date information. It does not have up-to-date information, so the call fails. If the initial `mt_transfer` call is part of a call chain originating from a call to `ft_transfer_call` on a fungible token, Bob's payment will be refunded and no assets will change hands. + +Note that approval all has different semantics, with approval all +the approval_id is linked to the owners entire state of the contract +as such approval all is vunerable to cross listing discrepencies. + +**Technical calls** + +Using near-cli notation for consistency: + + near call mt mt_transfer '{ + "receiver_id": "bob", + "token_id": "1", + "approval_id": 3, + }' --accountId bazaar --amount .000000000000000000000001 + +### 6. Revoke one + +Alice revokes Market's approval for this token. + +**Technical calls** + +Using near-cli: + + near call mt mt_revoke '{ + "account_id": "market", + "token_ids": ["1", "2"], + }' --accountId alice --amount .000000000000000000000001 + +Note that `market` will not get a cross-contract call in this case. The implementors of the Market app should implement [cron](https://en.wikipedia.org/wiki/Cron)-type functionality to intermittently check that Market still has the access they expect. + +### 7. Revoke all + +Alice revokes all approval for this token excluding approval all, approved +accounts. + +**Technical calls** + +Using near-cli: + + near call mt mt_revoke_all '{ + "token_ids": ["1", "2"], + }' --accountId alice --amount .000000000000000000000001 + +Again, note that no previous approvers will get cross-contract calls in this case. + +### 7. Revoke approval all + +Alice revokes approval all approvals for this account. This can take an +optional field for account_ids that specific specific accounts to revoke +or all accounts to revoke if field is None. + +**Technical calls** + +Using near-cli: + + near call mt mt_revoke_approval_all '{ + account_ids: None, + }' --accountId alice --amount .000000000000000000000001 + +Again, note that no previous approvers will get cross-contract calls in this case. + + + +## Reference-level explanation + +The `Token` structure returned by `mt_token` must include an `approvals` field, which is a map of account IDs to approval IDs. Using TypeScript's [Record type](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype) notation: + +```diff + type Token = { + id: string, + owner_id: string, ++ approvals: Record, + }; +``` + +Example token data: + +```json +{ + "id": "1", + "owner_id": "alice.near", + "approvals": { + "bob.near": 1, + "carol.near": 2, + } +} +``` + +### What is an "approval ID"? + +This is a unique number given to each approval that allows well-intentioned marketplaces or other 3rd-party MT resellers to avoid a race condition. The race condition occurs when: + +1. A token is listed in two marketplaces, which are both saved to the token as approved accounts. +2. One marketplace sells the token, which clears the approved accounts. +3. The new owner sells back to the original owner. +4. The original owner approves the token for the second marketplace again to list at a new price. But for some reason the second marketplace still lists the token at the previous price and is unaware of the transfers happening. +5. The second marketplace, operating from old information, attempts to again sell the token at the old price. + +Note that while this describes an honest mistake, the possibility of such a bug can also be taken advantage of by malicious parties via [front-running](https://defi.cx/front-running-ethereum/). + +To avoid this possibility, the MT contract generates a unique approval ID each time it approves an account. Then when calling `mt_transfer` or `mt_transfer_call`, the approved account passes `approval_id` with this value to make sure the underlying state of the token hasn't changed from what the approved account expects. + +Keeping with the example above, say the initial approval of the second marketplace generated the following `approvals` data: + +```json +{ + "id": "1", + "owner_id": "alice.near", + "approvals": { + "marketplace_1.near": 1, + "marketplace_2.near": 2, + } +} +``` + +But after the transfers and re-approval described above, the token might have `approvals` as: + +```json +{ + "id": "1", + "owner_id": "alice.near", + "approvals": { + "marketplace_2.near": 3, + } +} +``` + +The marketplace then tries to call `mt_transfer`, passing outdated information: + +```bash +# oops! +near call mt-contract.near mt_transfer '{ "approval_id": 2 }' +``` + + +### Interface + +The MT contract must implement the following methods: + +```ts +/******************/ +/* CHANGE METHODS */ +/******************/ + +// Add an approved account for a specific token. +// +// 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 `nft_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 `nft_on_approve` on `account_id`. See +// `nft_on_approve` description below for details. +// +// Arguments: +// * `token_id`: the token for which to add an approval +// * `account_id`: the account to add to `approvals` +// * `msg`: optional string to be passed to `nft_on_approve` +// +// Returns void, if no `msg` given. Otherwise, returns promise call to +// `nft_on_approve`, which can resolve with whatever it wants. +function mt_approve( + token_ids: Array, + 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 `nft_approve`, contract +// MUST refund associated storage deposit when owner revokes approval +// * Contract MUST panic if called by someone other than token owner +// +// Arguments: +// * `token_id`: the token for which to revoke an approval +// * `account_id`: the account to remove from `approvals` +function mt_revoke( + token_id: string, + account_id: string +) {} + +// Revoke an approval all account. Revokes access for that account for all tokens. +// +// Requirements +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security +// purposes +// * If contract requires >1yN deposit on `nft_approve`, contract +// MUST refund associated storage deposit when owner revokes approval +// * Contract MUST panic if called by someone other than token owner +// +// Arguments: +// * `account_id`: the account to remove from `approvals` +function mt_revoke_approval_all( + 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 `nft_approve`, contract +// MUST refund all associated storage deposit when owner revokes approvals +// * Contract MUST panic if called by someone other than token owner +// +// Arguments: +// * `token_id`: the token with approvals to revoke +function mt_revoke_all(token_id: string) {} + +// Revoke all approved accounts for approval all. +// +// Requirements +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security +// purposes +// * If contract requires >1yN deposit on `nft_approve`, contract +// MUST refund all associated storage deposit when owner revokes approvals +// * Contract MUST panic if called by someone other than token owner +// +// Arguments: +// * `token_id`: the token with approvals to revoke +function mt_revoke_all_approval_all() {} + +/****************/ +/* VIEW METHODS */ +/****************/ + +// Check if a token is approved for transfer by a given account, optionally +// checking an approval_id +// +// Arguments: +// * `token_id`: the token for which to revoke an approval +// * `approved_account_id`: the account to check the existence of in `approvals` +// * `approval_id`: an optional approval ID to check against current approval ID for given account +// +// Returns: +// if `approval_id` given, `true` if `approved_account_id` is approved with given `approval_id` +// otherwise, `true` if `approved_account_id` is in list of approved accounts +function mt_is_approved( + token_ids: [string], + approved_account_id: string, + approval_id: number|null +): boolean {} +``` + +### Why must `mt_approve` panic if `mt_revoke_all` would fail later? + +In the description of `mt_approve` above, it states: + + Contract MUST panic if addition would cause `mt_revoke_all` to exceed + single-block gas limit. + +What does this mean? + +First, it's useful to understand what we mean by "single-block gas limit". This refers to the [hard cap on gas per block at the protocol layer](https://docs.near.org/docs/concepts/gas#thinking-in-gas). This number will increase over time. + +Removing data from a contract uses gas, so if an MT had a large enough number of approvals, `mt_revoke_all` would fail, because calling it would exceed the maximum gas. + +Contracts must prevent this by capping the number of approvals for a given token. However, it is up to contract authors to determine a sensible cap for their contract (and the single block gas limit at the time they deploy). Since contract implementations can vary, some implementations will be able to support a larger number of approvals than others, even with the same maximum gas per block. + +Contract authors may choose to set a cap of something small and safe like 10 approvals, or they could dynamically calculate whether a new approval would break future calls to `mt_revoke_all`. But every contract MUST ensure that they never break the functionality of `mt_revoke_all`. + + +### Approved Account Contract Interface + +If a contract that gets approved to transfer MTs wants to, it can implement `mt_on_approve` or `mt_on_approve_all` 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_id`: the token to which this contract has been granted approval +// * `owner_id`: the owner of the token +// * `approval_id`: 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_id: [TokenId], + owner_id: string, + approval_id: number, + msg: string, +) {} +``` +```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_id`: the token to which this contract has been granted approval +// * `owner_id`: the owner of the token +// * `approval_id`: 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_all( + owner_id: string, + approval_id: 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 NFT 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. + +### No incurred cost for core MT behavior + +MT contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of `approvals` for calls to `mt_*` methods other than `mt_token`. See `near-contract-standards` [implementation of `ft_metadata` using `LazyOption`](https://github.com/near/near-sdk-rs/blob/c2771af7fdfe01a4e8414046752ee16fb0d29d39/examples/fungible-token/ft/src/lib.rs#L71) as a reference example. \ No newline at end of file From 46ae3b3975934ea418a5e586e755ee9eb7837243 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 14 Sep 2021 08:48:01 -0700 Subject: [PATCH 047/108] chore: clarify guide level explanation of balance --- specs/Standards/MultiToken/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 0d9b02d53..493cf8bcc 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -34,8 +34,8 @@ Discussions out of band: [guide-level-explanation]: #guide-level-explanation We should be able to do the following: -- Get balance of token per user -- Get balance in batch of a user +- Get balance of a single token_id per account per single transaction +- Get balance of multiple token_ids per account per single transaction - Get supply of tokens per id - Get supply of tokens in batch per id - Represent non fungibility of tokens From 6bae0ec8bb602e3e46a400353c600ece76a98629 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 14 Sep 2021 08:56:20 -0700 Subject: [PATCH 048/108] chore: clarify guide level explanation of supply --- specs/Standards/MultiToken/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 493cf8bcc..fae391187 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -36,8 +36,8 @@ Discussions out of band: We should be able to do the following: - Get balance of a single token_id per account per single transaction - Get balance of multiple token_ids per account per single transaction -- Get supply of tokens per id -- Get supply of tokens in batch per id +- Get total supply of a token by token_id per single transaction +- Get total supply of tokens per token_ids per single transaction - Represent non fungibility of tokens - Represent fungibility of tokens - Transfer tokens by id in batch From 6d735868d26154051f9802f920450f165e0d1e26 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 14 Sep 2021 08:58:45 -0700 Subject: [PATCH 049/108] chore: clarify transfer tokens guide-level explanation --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index fae391187..3ba8bee25 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -40,7 +40,7 @@ We should be able to do the following: - Get total supply of tokens per token_ids per single transaction - Represent non fungibility of tokens - Represent fungibility of tokens -- Transfer tokens by id in batch +- Transfer tokens by id in batch in a single transaction to a single account - Use these tokens on an exchange - Refund storage costs for fungible tokens From 9208acf67cf5bd9a23e2db84c4f2a9d8c44a60ae Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 14 Sep 2021 09:00:19 -0700 Subject: [PATCH 050/108] chore: reorder key for guide-level explanation --- specs/Standards/MultiToken/README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 3ba8bee25..792b6af5a 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -31,6 +31,15 @@ Discussions out of band: - https://github.com/shipsgold/multi-token-standard-impl/tree/main/meetings # Guide-level explanation + +There are a few concepts in the scenarios below: +- **Total supply**. It's the total number of tokens in circulation. +- **Balance owner**. An account ID that owns some amount of tokens. +- **Transfer**. Moves some amount from one account to another account. +- **Fungibility**. An indistinguishable amount of tokens to exchange +- **Non Fungibility**. Tokens that are differentiable from each other. + + [guide-level-explanation]: #guide-level-explanation We should be able to do the following: @@ -44,13 +53,6 @@ We should be able to do the following: - Use these tokens on an exchange - Refund storage costs for fungible tokens -There are a few concepts in the scenarios above: -- **Total supply**. It's the total number of tokens in circulation. -- **Balance owner**. An account ID that owns some amount of tokens. -- **Transfer**. Moves some amount from one account to another account. -- **Fungibility**. An indistinguishable amount of tokens to exchange -- **Non Fungibility**. Tokens that are differentiable from each other. - ### Real scenarios #### Simple transfer From 917894adf5d6890c8f6b568d43b98e41d79e457b Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 14 Sep 2021 17:20:57 -0700 Subject: [PATCH 051/108] chore: add missing comments for balance_bounds and balance_of --- specs/Standards/MultiToken/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 792b6af5a..f9ab70521 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -459,7 +459,14 @@ pub trait StorageManagement { /// Returns `false` if account was already unregistered. fn storage_unregister(&mut self, token_ids:Vec, force: Option) -> Vec; + /****************/ + /* VIEW METHODS */ + /****************/ + // Returns minimum and maximum allowed balance amounts to interact with this + // contract. See StorageBalanceBounds. fn storage_balance_bounds(&self, token_id:Vec, account_id: Option) -> StorageBalanceBounds; + + // If `account_id` is not registered, must return `null`. fn storage_balance_of(&self, token_ids:Vec, account_id: AccountId) -> Option; } ``` From aa3f0852bc7f0927245b2b62989719354ccc7c41 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 15 Sep 2021 16:50:46 -0700 Subject: [PATCH 052/108] fix: update standard to only support approvals by specific id --- .../MultiToken/ApprovalManagement.md | 284 ++++-------------- 1 file changed, 60 insertions(+), 224 deletions(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index 8c30ad60c..a9313568f 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -36,13 +36,12 @@ Alice and Bob are already [registered](README.md) with MT, Market, and Bazaar, a Let's examine the technical calls through the following scenarios: 1. [Simple approval](#1-simple-approval): Alice approves Bob to transfer her token. -2. [Simple approval all](#2-simple-approval-all): Alice approves Bob to transfer all her tokens and future tokens she may mint. -3. [Approval with cross-contract call (XCC)](#3-approval-with-cross-contract-call): Alice approves Market to transfer one of her tokens and passes `msg` so that NFT will call `mt_on_approve` on Market's contract. -4. [Approval with XCC, edge case](#4-approval-with-cross-contract-call-edge-case): Alice approves Bazaar and passes `msg` again, but what's this? Bazaar doesn't implement `mt_on_approve`, so Alice sees an error in the transaction result. Not to worry, though, she checks `mt_is_approved` and sees that she did successfully approve Bazaar, despite the error. -5. [Approval IDs](#5-approval-ids): Bob buys Alice's token via Market. -6. [Approval IDs, edge case](#6-approval-ids-edge-case): Bob transfers same token back to Alice, Alice re-approves Market & Bazaar. Bazaar has an outdated cache. Bob tries to buy from Bazaar at the old price. -7. [Revoke one](#7-revoke-one): Alice revokes Market's approval for this token. -8. [Revoke all](#8-revoke-all): Alice revokes all approval for this token. +2. [Approval with cross-contract call (XCC)](#2-approval-with-cross-contract-call): Alice approves Market to transfer one of her tokens and passes `msg` so that NFT will call `mt_on_approve` on Market's contract. +3. [Approval with XCC, edge case](#3-approval-with-cross-contract-call-edge-case): Alice approves Bazaar and passes `msg` again, but what's this? Bazaar doesn't implement `mt_on_approve`, so Alice sees an error in the transaction result. Not to worry, though, she checks `mt_is_approved` and sees that she did successfully approve Bazaar, despite the error. +4. [Approval IDs](#4-approval-ids): Bob buys Alice's token via Market. +5. [Approval IDs, edge case](#5-approval-ids-edge-case): Bob transfers same token back to Alice, Alice re-approves Market & Bazaar. Bazaar has an outdated cache. Bob tries to buy from Bazaar at the old price. +6. [Revoke one](#6-revoke-one): Alice revokes Market's approval for this token. +7. [Revoke all](#7-revoke-all): Alice revokes all approval for this token. ### 1. Simple Approval @@ -56,7 +55,7 @@ Alice approves Bob to transfer her token. **Technical calls** -1. Alice calls `mt::mt_approve({ "token_ids": ["1","2"], "account_id": "bob" })`. She attaches 1 yoctoⓃ, (.000000000000000000000001Ⓝ). Using [NEAR CLI](https://docs.near.org/docs/tools/near-cli) to make this call, the command would be: +1. Alice calls `mt::mt_approve({ "token_ids": ["1","2"], amounts:[1,100],"account_id": "bob" })`. She attaches 1 yoctoⓃ, (.000000000000000000000001Ⓝ). Using [NEAR CLI](https://docs.near.org/docs/tools/near-cli) to make this call, the command would be: near call mt mt_approve \ '{ "token_id": "1", "account_id": "bob" }' \ @@ -78,68 +77,20 @@ Alice approves Bob to transfer her token. "owner_id": "alice.near", "approvals": [{ "bob": 1, + "amount": 1, }, { "bob": 2, + "amount": 100, }] } 3. Alice calls view method `mt_is_approved`: near view mt mt_is_approved \ - '{ "token_ids": ["1", "2"], "approved_account_id": "bob" }' + '{ "token_ids": ["1", "2"], amounts:["1","100"], "approved_account_id": "bob" }' The response: - - true - -### 2. Simple Approval All - -Alice approves Bob for all token_ids to transfer her tokens. - -**High-level explanation** - -1. Alice approves Bob -2. Alice queries the token to verify -3. Alice verifies a different way - -**Technical calls** - -1. Alice calls `mt::mt_approve_all({"account_id": "bob" })`. She attaches 1 yoctoⓃ, (.000000000000000000000001Ⓝ). Using [NEAR CLI](https://docs.near.org/docs/tools/near-cli) to make this call, the command would be: - - near call mt mt_approve_all \ - '{"account_id": "bob"}' \ - --accountId alice --amount .000000000000000000000001 - - The response: - - '' - -2. Alice calls view method `mt_approvals`: - - near view mt mt_approvals \ - '{ "token_id": ["1", "2"] }' - - The response: - - { - "ids": ["1", "2"] - "owner_id": "alice.near", - "approvals": [{ - "bob": 1, - }, - { - "bob": 2, - }] - } - -3. Alice calls view method `mt_is_approved`: - - near view mt mt_is_approved \ - '{ "token_ids": ["1", "2"], "approved_account_id": "bob" }' - - The response: - true ### 3. Approval with cross-contract call @@ -158,8 +109,9 @@ Alice approves Market to transfer one of her tokens and passes `msg` so that MT near call mt mt_approve '{ "token_id": ["1","2"], + "amounts": ["1", "100"], "account_id": "market", - "msg": "{\"action\": \"list\", \"price\": \"100\", \"token\": \"nDAI\" }" + "msg": "{\"action\": \"list\", \"price\": [\"100\",\"50\"],\"token\": \"nDAI\" }" }' --accountId alice --amount .000000000000000000000001 At this point, near-cli will hang until the cross-contract call chain fully resolves, which would also be true if Alice used a Market frontend using [near-api-js](https://docs.near.org/docs/develop/front-end/near-api-js). Alice's part is done, though. The rest happens behind the scenes. @@ -168,48 +120,13 @@ Alice approves Market to transfer one of her tokens and passes `msg` so that MT near call market mt_on_approve '{ "token_id": ["1","2"], + "amounts": ["1","100"], "owner_id": "alice", "approval_id": 2, "msg": "{\"action\": \"list\", \"price\": [\"100\",\"50\"], \"token\": \"nDAI\" }" }' --accountId mt -3. `market` now knows that it can sell Alice's token for 100 [nDAI](https://explorer.mainnet.near.org/accounts/6b175474e89094c44da98b954eedeac495271d0f.factory.bridge.near), and that when it transfers it to a buyer using `mt_batch_transfer`, it can pass along the given `approval_id` to ensure that Alice hasn't changed her mind. It can schedule any further cross-contract calls it wants, and if it returns these promises correctly, Alice's initial near-cli call will resolve with the outcome from the final step in the chain. If Alice actually made this call from a Market frontend, the frontend can use this return value for something useful. - - - -### 3. Approval all with cross-contract call - -Alice approves Market to transfer some of her tokens and passes `msg` so that MT will call `mt_on_approve_all` on Market's contract. She probably does this via Market's frontend app which would know how to construct `msg` in a useful way. - -**High-level explanation** - -1. Alice calls `mt_approve_all` to approve `market` to transfer her tokens, and passes a `msg` -2. Since `msg` is included, `mt` will schedule a cross-contract call to `market` -3. Market can do whatever it wants with this info, such as listing the token for sale at a given price. The result of this operation is returned as the promise outcome to the original `mt_approve_all` call. This will allow the -market to transfer the listing whenver necessary. In addition to this any future listings as well that this owner owns. In the case of a game -new items in the collection can be listed for sale at no additional -approval storage cost, within the contract. - -**Technical calls** - -1. Using near-cli: - - near call mt mt_approve_all '{ - "account_id": "market", - "msg": "{\"action\": \"approve"}" - }' --accountId alice --amount .000000000000000000000001 - - At this point, near-cli will hang until the cross-contract call chain fully resolves, which would also be true if Alice used a Market frontend using [near-api-js](https://docs.near.org/docs/develop/front-end/near-api-js). Alice's part is done, though. The rest happens behind the scenes. - -2. `mt` schedules a call to `mt_on_approve` on `market`. Using near-cli notation for easy cross-reference with the above, this would look like: - - near call market mt_on_approve_all '{ - "owner_id": "alice", - "approval_id": 2, - "msg": "{\"action\": \"approve\"}" - }' --accountId mt - -3. `market` now knows that it can approve selling or using alice's token. It also knows that when alice sets a price and that when it transfers it to a buyer using `mt_batch_transfer`, it can pass along the given `approval_id` to ensure that Alice hasn't changed her mind. It can schedule any further cross-contract calls it wants, and if it returns these promises correctly, Alice's initial near-cli call will resolve with the outcome from the final step in the chain. If Alice actually made this call from a Market frontend, the frontend can use this return value for something useful. +3. `market` now knows that it can sell Alice's tokens for 100 [nDAI] and 50 [nDAI] (https://explorer.mainnet.near.org/accounts/6b175474e89094c44da98b954eedeac495271d0f.factory.bridge.near), and that when it transfers it to a buyer using `mt_batch_transfer`, it can pass along the given `approval_id` to ensure that Alice hasn't changed her mind. It can schedule any further cross-contract calls it wants, and if it returns these promises correctly, Alice's initial near-cli call will resolve with the outcome from the final step in the chain. If Alice actually made this call from a Market frontend, the frontend can use this return value for something useful. ### 3. Approval with cross-contract call, edge case @@ -229,7 +146,8 @@ Not to worry, though, she checks `mt_is_approved` and sees that she did successf 1. Using near-cli: near call mt mt_approve '{ - "token_id": "1", + "token_ids": ["1"], + "amounts: ["1000"], "account_id": "bazaar", "msg": "{\"action\": \"list\", \"price\": \"100\", \"token\": \"nDAI\" }" }' --accountId alice --amount .000000000000000000000001 @@ -237,7 +155,8 @@ Not to worry, though, she checks `mt_is_approved` and sees that she did successf 2. `mt` schedules a call to `mt_on_approve` on `market`. Using near-cli notation for easy cross-reference with the above, this would look like: near call bazaar mt_on_approve '{ - "token_id": ["1","2"], + "token_ids": ["1"], + "amounts": ["1000"], "owner_id": "alice", "approval_id": 3, "msg": "{\"action\": \"list\", \"price\": \"100\", \"token\": \"nDAI\" }" @@ -248,54 +167,12 @@ Not to worry, though, she checks `mt_is_approved` and sees that she did successf 4. Alice checks if the approval itself worked, despite the error on the cross-contract call: near view mt mt_is_approved \ - '{ "token_id": "1", "approved_account_id": "bazaar" }' + '{ "token_ids": ["1","2"], "amounts":["1","100"], "approved_account_id": "bazaar" }' The response: true -### 3. Approval all with cross-contract call, edge case - -Alice approves Bazaar and passes `msg` again. Maybe she actually does this via near-cli, rather than using Bazaar's frontend, because what's this? Bazaar doesn't implement `mt_on_approve_all`, so Alice sees an error in the transaction result. - -Not to worry, though, she checks `mt_is_approved` and sees that she did successfully approve Bazaar, despite the error. She will have to find a new way to list her token for sale in Bazaar, rather than using the same `msg` shortcut that worked for Market. - -**High-level explanation** - -1. Alice calls `mt_approve_all` to approve `bazaar` to transfer her token, and passes a `msg`. -2. Since `msg` is included, `mt` will schedule a cross-contract call to `bazaar`. -3. Bazaar doesn't implement `mt_on_approve_all`, so this call results in an error. The approval still worked, but Alice sees an error in her near-cli output. -4. Alice checks if `bazaar` is approved, and sees that it is, despite the error. - -**Technical calls** - -1. Using near-cli: - - near call mt mt_approve_all '{ - "account_id": "bazaar", - "msg": "{\"action\": \"approve\"}" - }' --accountId alice --amount .000000000000000000000001 - -2. `mt` schedules a call to `mt_on_approve_all` on `market`. Using near-cli notation for easy cross-reference with the above, this would look like: - - near call bazaar mt_on_approve_all '{ - "owner_id": "alice", - "approval_id": 3, - "msg": "{\"action\": \"approve\" }" - }' --accountId mt - -3. 💥 `bazaar` doesn't implement this method, so the call results in an error. Alice sees this error in the output from near-cli. - -4. Alice checks if the approval itself worked, despite the error on the cross-contract call: - - near view mt mt_is_approved \ - '{ "token_id": "1", "approved_account_id": "bazaar" }' - - The response: - - true - - ### 4. Approval IDs Bob buys Alice's token via Market. Bob probably does this via Market's frontend, which will probably initiate the transfer via a call to `ft_transfer_call` on the nDAI contract to transfer 100 nDAI to `market`. Like the NFT standard's "transfer and call" function, [Fungible Token](../FungibleToken/Core.md)'s `ft_transfer_call` takes a `msg` which `market` can use to pass along information it will need to pay Alice and actually transfer the MT. The actual transfer of the MT is the only part we care about here. @@ -352,8 +229,7 @@ Note that `market` will not get a cross-contract call in this case. The implemen ### 7. Revoke all -Alice revokes all approval for this token excluding approval all, approved -accounts. +Alice revokes all approval for these tokens **Technical calls** @@ -365,33 +241,20 @@ Using near-cli: Again, note that no previous approvers will get cross-contract calls in this case. -### 7. Revoke approval all - -Alice revokes approval all approvals for this account. This can take an -optional field for account_ids that specific specific accounts to revoke -or all accounts to revoke if field is None. - -**Technical calls** - -Using near-cli: - - near call mt mt_revoke_approval_all '{ - account_ids: None, - }' --accountId alice --amount .000000000000000000000001 - -Again, note that no previous approvers will get cross-contract calls in this case. - - ## Reference-level explanation The `Token` structure returned by `mt_token` must include an `approvals` field, which is a map of account IDs to approval IDs. Using TypeScript's [Record type](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype) notation: ```diff + interface Approval = { + amount: number + approval_id: string + } type Token = { id: string, owner_id: string, -+ approvals: Record, ++ approvals: Record, }; ``` @@ -402,8 +265,14 @@ Example token data: "id": "1", "owner_id": "alice.near", "approvals": { - "bob.near": 1, - "carol.near": 2, + "bob.near": { + "amount": 100, + "approval_id":1, + }, + "carol.near": { + "amount":2, + "approval_id": 2, + } } } ``` @@ -429,8 +298,13 @@ Keeping with the example above, say the initial approval of the second marketpla "id": "1", "owner_id": "alice.near", "approvals": { - "marketplace_1.near": 1, + "marketplace_1.near": { + "approval_id": 1, + "amount": "100", + }, "marketplace_2.near": 2, + "approval_id": 2, + "amount": "50", } } ``` @@ -442,7 +316,10 @@ But after the transfers and re-approval described above, the token might have `a "id": "1", "owner_id": "alice.near", "approvals": { - "marketplace_2.near": 3, + "marketplace_2.near": { + "approval_id": 3, + "amount": "50", + } } } ``` @@ -480,14 +357,16 @@ The MT contract must implement the following methods: // `nft_on_approve` description below for details. // // Arguments: -// * `token_id`: the token for which to add an approval +// * `token_ids`: the token ids for which to add an approval // * `account_id`: the account to add to `approvals` +// * `amounts`: the corresponding token_id amounts to add to `approvals` // * `msg`: optional string to be passed to `nft_on_approve` // // Returns void, if no `msg` given. Otherwise, returns promise call to // `nft_on_approve`, which can resolve with whatever it wants. function mt_approve( token_ids: Array, + amounts:Array, account_id: string, msg: string|null, ): void|Promise {} @@ -502,25 +381,10 @@ function mt_approve( // * Contract MUST panic if called by someone other than token owner // // Arguments: -// * `token_id`: the token for which to revoke an approval +// * `token_ids`: the token for which to revoke an approval // * `account_id`: the account to remove from `approvals` function mt_revoke( - token_id: string, - account_id: string -) {} - -// Revoke an approval all account. Revokes access for that account for all tokens. -// -// Requirements -// * Caller of the method must attach a deposit of 1 yoctoⓃ for security -// purposes -// * If contract requires >1yN deposit on `nft_approve`, contract -// MUST refund associated storage deposit when owner revokes approval -// * Contract MUST panic if called by someone other than token owner -// -// Arguments: -// * `account_id`: the account to remove from `approvals` -function mt_revoke_approval_all( + token_ids: Array, account_id: string ) {} @@ -534,21 +398,8 @@ function mt_revoke_approval_all( // * Contract MUST panic if called by someone other than token owner // // Arguments: -// * `token_id`: the token with approvals to revoke -function mt_revoke_all(token_id: string) {} - -// Revoke all approved accounts for approval all. -// -// Requirements -// * Caller of the method must attach a deposit of 1 yoctoⓃ for security -// purposes -// * If contract requires >1yN deposit on `nft_approve`, contract -// MUST refund all associated storage deposit when owner revokes approvals -// * Contract MUST panic if called by someone other than token owner -// -// Arguments: -// * `token_id`: the token with approvals to revoke -function mt_revoke_all_approval_all() {} +// * `token_ids`: the token ids with approvals to revoke +function mt_revoke_all(token_ids: Array) {} /****************/ /* VIEW METHODS */ @@ -561,13 +412,16 @@ function mt_revoke_all_approval_all() {} // * `token_id`: the token for which to revoke an approval // * `approved_account_id`: the account to check the existence of in `approvals` // * `approval_id`: an optional approval ID to check against current approval ID for given account +// * `amounts`: specify the positionally corresponding amount for the token_id that at least must be approved // // Returns: -// if `approval_id` given, `true` if `approved_account_id` is approved with given `approval_id` -// otherwise, `true` if `approved_account_id` is in list of approved accounts +// if `approval_id` 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: [number], approval_id: number|null ): boolean {} ``` @@ -592,7 +446,7 @@ Contract authors may choose to set a cap of something small and safe like 10 app ### Approved Account Contract Interface -If a contract that gets approved to transfer MTs wants to, it can implement `mt_on_approve` or `mt_on_approve_all` to update its own state when granted approval for a token: +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. @@ -601,35 +455,17 @@ If a contract that gets approved to transfer MTs wants to, it can implement `mt_ // * Contract knows the token contract ID from `predecessor_account_id` // // Arguments: -// * `token_id`: the token to which this contract has been granted approval +// * `token_ids`: the token_ids to which this contract has been granted approval // * `owner_id`: the owner of the token +// * `amounts`: the amounts to which this contract has been granted approval // * `approval_id`: 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_id: [TokenId], - owner_id: string, - approval_id: number, - msg: string, -) {} -``` -```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_id`: the token to which this contract has been granted approval -// * `owner_id`: the owner of the token -// * `approval_id`: 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_all( + token_ids: [TokenId], + amounts: [number], owner_id: string, approval_id: number, msg: string, From 04f5a41e34fbcab780e7a9197796a384d7d64b75 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 15 Sep 2021 19:11:37 -0700 Subject: [PATCH 053/108] feat: refactor and add approval management back into the spec --- specs/Standards/MultiToken/README.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index f9ab70521..8704ce092 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -135,6 +135,7 @@ Technical calls: sender_id: AccountId, token_ids: Vec, amounts: Vec, + approval_ids: Option>, msg: String, ) -> PromiseOrValue>; } @@ -178,6 +179,7 @@ Technical calls: sender_id: AccountId, token_ids: Vec, amounts: Vec, + previous_owner_id: AccountId, msg: String, ) -> PromiseOrValue>; } @@ -205,6 +207,9 @@ pub trait MultiTokenCore { /// * `receiver_id`: the valid NEAR account receiving the token /// * `token_id`: the token or tokens to transfer /// * `amount`: the token amount of tokens to transfer for token_id + /// * `approval_id`: 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 fn mt_transfer( @@ -212,6 +217,7 @@ pub trait MultiTokenCore { receiver_id: AccountId, token_id: TokenId, amount: U128, + approval_id: Option, memo: Option, ); @@ -237,6 +243,9 @@ pub trait MultiTokenCore { /// * `receiver_id`: the valid NEAR account receiving the token. /// * `token_id`: the token to send. /// * `amount`: amount of tokens to transfer for token_id + /// * `approval_id`: 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. /// * `msg`: specifies information needed by the receiving contract in @@ -247,6 +256,7 @@ pub trait MultiTokenCore { receiver_id: AccountId, token_id: TokenId, amount: U128, + approval_id: Option, memo: Option, msg: String, ) -> PromiseOrValue; @@ -274,7 +284,7 @@ pub trait MultiTokenCore { /// * `amounts`: the amount of tokens to transfer for corresponding token_id /// * `approval_ids`: expected approval ID. A number smaller than /// 2^53, and therefore representable as JSON. See Approval Management - /// standard for full explanation. Must have same length as token_ids + /// standard for full explanation. Must have same length and order as /// token_ids /// * `memo` (optional): for use cases that may benefit from indexing or /// providing information for a transfer @@ -283,6 +293,7 @@ pub trait MultiTokenCore { receiver_id: AccountId, token_ids: Vec, amounts: Vec, + approval_ids: Option>>, memo: Option, ); /// Batch transfer token/s and call a method on a receiver contract. A successful @@ -311,8 +322,9 @@ pub trait MultiTokenCore { /// * `receiver_id`: the valid NEAR account receiving the token. /// * `token_ids`: the tokens to transfer /// * `amounts`: the amount of tokens to transfer for corresponding token_id - /// * `approval_ids`: expected approval IDs. A number smaller than + /// * `approval_ids`: expected approval ID. A number smaller than /// 2^53, and therefore representable as JSON. See Approval Management + /// standard for full explanation. Must have same length and order as /// token_ids /// standard for full explanation. Must have same length as token_ids /// * `memo` (optional): for use cases that may benefit from indexing or /// providing information for a transfer. @@ -325,6 +337,7 @@ pub trait MultiTokenCore { receiver_id: AccountId, token_ids: Vec, amounts: Vec, + approval_ids: Option>>, memo: Option, msg: String, ) -> PromiseOrValue>; @@ -369,6 +382,7 @@ pub trait MultiTokenReceiver { fn mt_on_transfer( &mut self, sender_id: AccountId, + previous_owner_id: AccountId, token_ids: Vec, amounts: Vec, msg: String, @@ -402,9 +416,10 @@ pub trait MultiTokenResolver { /// * `previous_owner_id`: the owner prior to the call to `mt_transfer_call` /// * `receiver_id`: the `receiver_id` argument given to `mt_transfer_call` /// * `token_ids`: the `token_ids` argument given to `mt_transfer_call` - /// * `approvals`: if using Approval Management, contract MUST provide + /// * `approved_account_ids`: if using Approval Management, contract MUST provide /// set of original approved accounts in this argument, and restore these - /// approved accounts in case of revert. In this case it may be multiple sets of approvals + /// approved accounts in case of revert. In this case it may be multiple sets of approvals . If specified the length and order must correspond to token_ids + /// /// Returns true if tokens were successfully transferred to `receiver_id`. fn mt_resolve_transfer( @@ -413,6 +428,7 @@ pub trait MultiTokenResolver { receiver_id: AccountId, token_ids: Vec, amounts: Vec, + approved_account_ids: Option>>, ) -> Vec; } ``` From 2e56a4d344da79f512448b90d4155789f57ca69f Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 17 Sep 2021 12:54:38 -0700 Subject: [PATCH 054/108] chore: tidy Co-authored-by: Daryl Collins --- specs/Standards/MultiToken/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 8704ce092..1ede8466b 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -342,7 +342,7 @@ pub trait MultiTokenCore { msg: String, ) -> PromiseOrValue>; - /// Get the balance of an an account given token_id. For fungible token returns back amount, for + /// Get the balance of an account for the given `token_id`. For fungible token returns back amount, for /// non fungible token it returns back constant 1. fn mt_balance_of(&self, owner_id: AccountId, token_id: TokenId) -> U128; From 799f5bc3203efa593d1008bc68f2b20d26a122c6 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 22 Sep 2021 20:26:51 -0700 Subject: [PATCH 055/108] fix: refactor metadata to split out metadata into it's own md --- specs/Standards/MultiToken/Metadata.md | 130 +++++++++++++++++++++++++ specs/Standards/MultiToken/README.md | 36 ++----- 2 files changed, 137 insertions(+), 29 deletions(-) create mode 100644 specs/Standards/MultiToken/Metadata.md diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md new file mode 100644 index 000000000..6ed37baf7 --- /dev/null +++ b/specs/Standards/MultiToken/Metadata.md @@ -0,0 +1,130 @@ +# Multi Token Metadata + +Version `1.0.0` + +## Summary + +An interface for a multi token's 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. + +## Motivation + +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 NFT's metadata. + +NEAR's unique [storage staking](https://docs.near.org/docs/concepts/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: #246 + +## Interface + +Metadata applies at both the class level (`BaseTokenMetadata`) and the specific instance level (`TokenMetadata`). 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 NFT 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. +} +``` + +A new set of functions MUST be supported on the MT contract: + +```ts +// Returns the top-level contract level metadtata +function mt_metadata(): ContractMetadata {} +function mt_token_metadata_by_token_id(token_ids: TokenId[]): TokenMetadata[] +function mt_base_token_metadata_by_token_id(token_ids: TokenId[]): BaseTokenMetadata[] +function mt_base_token_metadata(base_metadata_ids: BaseMetadataId[]):BaseTokenMetadata + +``` + +A new attribute MUST be added to each `Token` struct: + +```diff + type Token = { + id: string, + owner_id: string, ++ metadata: TokenMetadata, ++ base_metadata_id: string, + } +``` + +### 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/docs/concepts/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", "https://example.com/token.json", 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`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token was issued or minted +- `expires_at`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token expires +- `starts_at`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token starts being valid +- `updated_at`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token was last updated +- `extra`: anything extra the NFT 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. + +### No incurred cost for core NFT behavior + +Contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of metadata for calls to `mt_*` methods other than `mt_*metadata*` or `mt_token`. See `near-contract-standards` [implementation using `LazyOption`](https://github.com/near/near-sdk-rs/blob/c2771af7fdfe01a4e8414046752ee16fb0d29d39/examples/fungible-token/ft/src/lib.rs#L71) as a reference example. + +## Drawbacks + +* When this MT contract is created and initialized, the storage use per-token will be higher than an MT Core version. Frontends can account for this by adding extra deposit when minting. This could be done by padding with a reasonable amount, or by the frontend using the [RPC call detailed here](https://docs.near.org/docs/develop/front-end/rpc#genesis-config) that gets genesis configuration and actually determine precisely how much deposit is needed. +* Convention of `icon` being a data URL rather than a link to an HTTP endpoint that could contain privacy-violating code cannot be done on deploy or update of contract metadata, and must be done on the consumer/app side when displaying token data. +* If on-chain icon uses a data URL or is not set but the document given by `reference` contains a privacy-violating `icon` URL, consumers & apps of this data should not naïvely display the `reference` version, but should prefer the safe version. This is technically a violation of the "`reference` setting wins" policy described above. + +## Future possibilities + +- Detailed conventions that may be enforced for versions. +- A fleshed out schema for what the `reference` object should contain. diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index 1ede8466b..fb47f8c64 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -191,6 +191,13 @@ Technical calls: WIP implementation: https://github.com/shipsgold/multi-token-standard-impl/tree/feat/initial-token ### Core Trait ``` +// The base structure that will be returned for a token. If contract is using +// extensions such as Approval Management, Metadata, or other +// attributes may be included in this structure. +pub trait Token { + id: string, +} + pub trait MultiTokenCore { /// Basic token transfer. Transfer a token or tokens given a token_id. The token id can correspond to /// either a NonFungibleToken or FungibleToken - this is differentiated by the implementation. @@ -487,35 +494,6 @@ pub trait StorageManagement { } ``` -### Metadata Trait -``` -pub struct MultiTokenMetadata { - pub spec: String, // required, essentially a version like "mt-1.0.0" - pub name: String, // required, ex. "Mosaics" - pub symbol: Option, // ex. "MOSIAC" - pub icon: Option, // Data URL - pub base_uri: Option, // Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs - // supports metadata_uri interface that interpolates {id} in the string - pub decimals: Option, // Option to specify precision if required - pub title: Option, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" - pub description: Option, // free-form description - pub media: Option, // URL to associated media, preferably to decentralized, content-addressed storage - pub media_hash: Option, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. - pub copies: Option, // number of copies of this set of metadata in existence when token was minted. - pub issued_at: Option, // ISO 8601 datetime when token was issued or minted - pub expires_at: Option, // ISO 8601 datetime when token expires - pub starts_at: Option, // ISO 8601 datetime when token starts being valid - pub updated_at: Option, // ISO 8601 datetime when token was last updated - pub extra: Option, // anything extra the NFT wants to store on-chain. Can be stringified JSON. - pub reference: Option, // URL to an off-chain JSON file with more info. - pub reference_hash: Option, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. -} - -/// Offers details on the metadata. -pub trait MultiTokenMetadataProvider { - fn mt_metadata(&self, token_id: TokenId) -> MultiTokenMetadata; -} -``` # Drawbacks [drawbacks]: #drawbacks Doing this adds another spec and codebase to the standards. It could be seen that we could leave this to developers to implement custom solutions and have them create a contract that implements both `NEP-141` and `NEP-171` methods together. There is some additional complexity From 0339e58e43941a2472f155dc593b48a0eb928ebe Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 22 Sep 2021 20:27:22 -0700 Subject: [PATCH 056/108] feat: add enumeration standard to multi token standard --- specs/Standards/MultiToken/Enumeration.md | 80 +++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 specs/Standards/MultiToken/Enumeration.md diff --git a/specs/Standards/MultiToken/Enumeration.md b/specs/Standards/MultiToken/Enumeration.md new file mode 100644 index 000000000..7a2f0347e --- /dev/null +++ b/specs/Standards/MultiToken/Enumeration.md @@ -0,0 +1,80 @@ +# Multi Token Enumeration + +Version `1.0.0` + +## Summary + +Standard interfaces for counting & fetching tokens, for an entire Multi Token contract or for a given owner. + +## Motivation + +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 +- [NEP-181]'s enumeration extension + +## Interface + +The contract must implement the following view methods: + +```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 above, 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 BaseTokenMetadata objects, as described above, and an empty array if there are no tokens +function mt_base_token_metadata( + from_index: string | null, + limit: number | null + ): BaseTokenMetadata[] +``` + + +## Notes + +At the time of this writing, the specialized collections in the `near-sdk` Rust crate are iterable, but not all of them have implemented an `iter_from` solution. There may be efficiency gains for large collections and contract developers are encouraged to test their data structures with a large amount of entries. + + [ERC-721]: https://eips.ethereum.org/EIPS/eip-721 + [storage]: https://docs.near.org/docs/concepts/storage-staking From c148a83f32bd95a99702d411f558a9dedff35fc5 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Mon, 27 Sep 2021 18:43:37 -0700 Subject: [PATCH 057/108] chore: fix typos --- specs/Standards/MultiToken/ApprovalManagement.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index a9313568f..52c759db5 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -341,7 +341,7 @@ The MT contract must implement the following methods: /* CHANGE METHODS */ /******************/ -// Add an approved account for a specific token. +// 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 @@ -360,10 +360,10 @@ The MT contract must implement the following methods: // * `token_ids`: the token ids for which to add an approval // * `account_id`: the account to add to `approvals` // * `amounts`: the corresponding token_id amounts to add to `approvals` -// * `msg`: optional string to be passed to `nft_on_approve` +// * `msg`: optional string to be passed to `mt_on_approve` // // Returns void, if no `msg` given. Otherwise, returns promise call to -// `nft_on_approve`, which can resolve with whatever it wants. +// `mt_on_approve`, which can resolve with whatever it wants. function mt_approve( token_ids: Array, amounts:Array, @@ -381,7 +381,7 @@ function mt_approve( // * Contract MUST panic if called by someone other than token owner // // Arguments: -// * `token_ids`: the token for which to revoke an approval +// * `token_ids`: the token for which to revoke approvals // * `account_id`: the account to remove from `approvals` function mt_revoke( token_ids: Array, @@ -405,11 +405,11 @@ function mt_revoke_all(token_ids: Array) {} /* VIEW METHODS */ /****************/ -// Check if a token is approved for transfer by a given account, optionally +// Check if tokens are approved for transfer by a given account, optionally // checking an approval_id // // Arguments: -// * `token_id`: the token for which to revoke an approval +// * `token_ids`: the tokens for which to check an approval // * `approved_account_id`: the account to check the existence of in `approvals` // * `approval_id`: an optional approval ID to check against current approval ID for given account // * `amounts`: specify the positionally corresponding amount for the token_id that at least must be approved From d5e0752f4b77907470138a0eaf86de26d78c0749 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Mon, 27 Sep 2021 18:44:57 -0700 Subject: [PATCH 058/108] fix: add MT prefixed datastructure descritpion and mt_prefixed methods --- specs/Standards/MultiToken/Enumeration.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/specs/Standards/MultiToken/Enumeration.md b/specs/Standards/MultiToken/Enumeration.md index 7a2f0347e..f9cb51329 100644 --- a/specs/Standards/MultiToken/Enumeration.md +++ b/specs/Standards/MultiToken/Enumeration.md @@ -22,8 +22,14 @@ Prior art: The contract must implement the following view methods: ```ts - - +// 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. +type MTToken { + id: string + metadata?: { + base_metadata_id: string + token_metadata: MTTokenMetadata + } +} // Get a list of all tokens // @@ -32,11 +38,11 @@ The contract must implement the following view methods: // representing the starting index of tokens to return // * `limit`: the maximum number of tokens to return // -// Returns an array of Token objects, as described above, and an empty array if there are no tokens +// Returns an array of MTToken objects, as described above, 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[] {} +): MTToken[] {} // Get list of all tokens owned by a given account // @@ -51,7 +57,7 @@ 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[] {} +): MTToken[] {} ``` The contract must implement the following view methods if using metadata extension: @@ -64,8 +70,8 @@ The contract must implement the following view methods if using metadata extensi // representing the starting index of tokens to return // * `limit`: the maximum number of tokens to return // -// Returns an array of BaseTokenMetadata objects, as described above, and an empty array if there are no tokens -function mt_base_token_metadata( +// Returns an array of MTBaseTokenMetadata objects, as described above, and an empty array if there are no tokens +function mt_tokens_base_metadata_all( from_index: string | null, limit: number | null ): BaseTokenMetadata[] From 90451e32b4509342f3250fd05e3dc1dfd34daaa1 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Mon, 27 Sep 2021 18:46:26 -0700 Subject: [PATCH 059/108] fix: refactor to have MT and mt_metadata prefixes and describe new MT metadata structures --- specs/Standards/MultiToken/Metadata.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index 6ed37baf7..dfbd70bb2 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -53,20 +53,25 @@ type MTTokenMetadata = { 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 NFT wants to store on-chain. Can be stringified JSON. + 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(): ContractMetadata {} -function mt_token_metadata_by_token_id(token_ids: TokenId[]): TokenMetadata[] -function mt_base_token_metadata_by_token_id(token_ids: TokenId[]): BaseTokenMetadata[] -function mt_base_token_metadata(base_metadata_ids: BaseMetadataId[]):BaseTokenMetadata +function mt_metadata_contract(): MTContractMetadata {} +function mt_metadata_token_all(token_ids: TokenId[]): MTAllTokenMetadata[] +function mt_metadata_token_by_token_id(token_ids: TokenId[]): MTTokenMetadata[] +function mt_metadata_base_by_token_id(token_ids: TokenId[]): MTBaseMetadata[] +function mt_metadata_base_by_metadata_id(base_metadata_ids: MTBaseMetadataId[]):MTBaseTokenMetadata ``` @@ -75,8 +80,7 @@ A new attribute MUST be added to each `Token` struct: ```diff type Token = { id: string, - owner_id: string, -+ metadata: TokenMetadata, ++ metadata: MTTokenMetadata, + base_metadata_id: string, } ``` @@ -110,10 +114,15 @@ For `MTTokenMetadata`: - `expires_at`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token expires - `starts_at`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token starts being valid - `updated_at`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token was last updated -- `extra`: anything extra the NFT wants to store on-chain. Can be stringified JSON. +- `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 `MTAllTokenMetadata`: + +- `base`: The base metadata that corresonds to `MTBaseTokenMetadata` for the token. +- `token`: The token specific metadata that corresponds to `MTTokenMetadata`. + ### No incurred cost for core NFT behavior Contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of metadata for calls to `mt_*` methods other than `mt_*metadata*` or `mt_token`. See `near-contract-standards` [implementation using `LazyOption`](https://github.com/near/near-sdk-rs/blob/c2771af7fdfe01a4e8414046752ee16fb0d29d39/examples/fungible-token/ft/src/lib.rs#L71) as a reference example. From 561ba59e1710dc6b2574e08b68178298a7fed4ce Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Mon, 27 Sep 2021 18:49:36 -0700 Subject: [PATCH 060/108] chore: fix typos --- specs/Standards/MultiToken/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index fb47f8c64..be25e483f 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -385,7 +385,10 @@ pub trait MultiTokenReceiver { /// * `msg`: information necessary for this contract to know how to process the /// request. This may include method names and/or arguments. /// - /// Returns true if tokens should be returned to `sender_id` + // Returns a value, or a promise which resolves with a vector of values. The values are the + /// number of unused tokens in string form. For instance, if `amount` is 10 but only 9 are + /// needed, it will return ["1"]. + fn mt_on_transfer( &mut self, sender_id: AccountId, @@ -427,26 +430,23 @@ pub trait MultiTokenResolver { /// set of original approved accounts in this argument, and restore these /// approved accounts in case of revert. In this case it may be multiple sets of approvals . If specified the length and order must correspond to token_ids - /// - /// Returns true if tokens were successfully transferred to `receiver_id`. + /// Returns the amount of how many total tokens were spent by `sender_id`, corresponding to the `token_id`. fn mt_resolve_transfer( &mut self, sender_id: AccountId, receiver_id: AccountId, token_ids: Vec, amounts: Vec, - approved_account_ids: Option>>, + approved_account_ids: Option>>>, ) -> Vec; } ``` ### Storage Management Trait -#### Notes -This is semi necessary for ft token types to be able to refund users for storage of many different token types like gold/silver... this might be slightly out of scope ``` pub trait StorageManagement { // if `registration_only=true` MUST refund above the minimum balance if the account didn't exist and // refund full deposit if the account exists. - fn storage_deposit( + fn mt_storage_deposit( &mut self, token_ids: Vec, account_id: Option, From 77ada2101e596090104db772df90c0b6984fc346 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 28 Sep 2021 15:49:42 -0700 Subject: [PATCH 061/108] fix: refactor Core description into typescript --- specs/Standards/MultiToken/Core.md | 349 +++++++++++++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 specs/Standards/MultiToken/Core.md diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md new file mode 100644 index 000000000..6647d78d4 --- /dev/null +++ b/specs/Standards/MultiToken/Core.md @@ -0,0 +1,349 @@ +# Multi Token ([NEP-246](https://github.com/near/NEPs/discussions/246)) + +Version `1.0.0` + +## Summary + +A standard interface for a multi token standard that supports fungible, semi-fungible, and tokens of any type, allowing for ownership, transfer, and batch transfer of tokens regardless of specific type. + +## Motivation + + +In the three years since [ERC-1155] was ratified by the Ethereum Community, Multi Token based contracts have proven themselves valuable assets. Many blockchain projects emulate this standard for representing multiple token assets classes in a single contract. The ability to reduce transaction overhead for marketplaces, video games, daos, and exchanges is appealing to the blockchain ecosystem and simplifies transactions for developers. + +Having a single contract represent NFTs, FTs, and tokens that sit inbetween greatly improves efficiency. The standard also introdued the ability to make batch requests with multiple asset classes reducing complexity. This standard allows operations that currently require _many_ transactions to be completed in a single transaction that can transfer not only NFTs and FTs, but any tokens that are a part of same token contract. + +With this standard, we have sought to take advantage of the ability of the NEAR bockchain to scale. It's sharded runtime, and [storage staking] model that decouples [gas] fees from storage demand, enables ultra low transaction fees and greater on chain storage ( see [Metadata]extenson). + +With the aformentioned, it is note worthy to mention that like the [NFT] standard the Multi Token standard, implements `mt_transfer_call`, +which allows, a user to attach many tokens to a call to a separate contract. Additionally this standard includes an optional [Approval Management] extension. The extension allows marketplaces to trade on behalf of a user, providing additional flexibility for dApps. + +Prior art: + +- [ERC-721] +- [EIP-1155 for multi-tokens](https://eips.ethereum.org/EIPS/eip-1155) +- [NEAR's Fungible Token Standard](../FungibleToken/Core.md), which first pioneered the "transfer and call" technique +- [NEAR's Non-Fungible Token Standard](../NearFungibleToken/Core.md) + +## Reference-level explanation + +**NOTES**: +- All amounts, balances and allowance are limited by `U128` (max value `2**128 - 1`). +- Token standard uses JSON for serialization of arguments and results. +- Amounts in arguments and results have are serialized as Base-10 strings, e.g. `"100"`. This is done to avoid JSON limitation of max integer value of `2**53`. +- The contract must track the change in storage when adding to and removing from collections. This is not included in this core multi token standard but instead in the [Storage Standard](../StorageManagement.md). +- To prevent the deployed contract from being modified or deleted, it should not have any access keys on its account. + +### MT Interface + +```ts +// The base structure that will be returned for a token. If contract is using +// extensions such as Approval Management, Metadata, or other +// attributes may be included in this structure. +type Token = { + id: string, +} + +/******************/ +/* CHANGE METHODS */ +/******************/ + +// Simple transfer. Transfer a given `token_id` from current owner to +// `receiver_id`. +// +// Requirements +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes +// * Caller must have greater than or equal to the `amount` being requested +// * Contract MUST panic if called by someone other than token owner or, +// if using Approval Management, one of the approved accounts +// * `approval_id` is for use with Approval Management extension, see +// that document for full explanation. +// * If using Approval Management, contract MUST nullify approved accounts on +// successful transfer. +// +// Arguments: +// * `receiver_id`: the valid NEAR account receiving the 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_id`: 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, + approval_id: number|null, + memo: string|null, +) {} + +// Simple batch transfer. Transfer a given `token_ids` from current owner to +// `receiver_id`. +// +// Requirements +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes +// * Caller must have greater than or equal to the `amounts` being requested for the given `token_ids` +// * Contract MUST panic if called by someone other than token owner or, +// if using Approval Management, one of the approved accounts +// * `approval_id` is for use with Approval Management extension, see +// that document for full explanation. +// * If using Approval Management, contract MUST nullify approved accounts on +// successful transfer. +// * Contract MUST panic if called with the length of `token_ids` not equal to `amounts` is not equal +// * Contract MUST panic if `approval_ids` is not `null` and does not equal the length of `token_ids` +// +// Arguments: +// * `receiver_id`: the valid NEAR account receiving the token +// * `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. +// * `approval_ids`: expected approval IDs per `token_ids`. If a `token_id` does +// not have a corresponding approval id then the entry in the array must be marked null. +// The `approval_ids` are numbers smaller than 2^53, and therefore representable as JSON. +// ApprovalId 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[], + approval_ids: (number | null)[] | null, + memo: string|null, +) {} + +// Returns `true` if the token was transferred from the sender's account. + +// Transfer token and call a method on a receiver contract. A successful +// workflow will end in a success execution outcome to the callback on the MT +// contract at the method `mt_resolve_transfer`. +// +// You can think of this as being similar to attaching native NEAR tokens to a +// function call. It allows you to attach any Multi Token, token in a call to a +// receiver contract. +// +// Requirements: +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security +// purposes +// * Caller must have greater than or equal to the `amount` being requested +// * Contract MUST panic if called by someone other than token owner or, +// if using Approval Management, one of the approved accounts +// * The receiving contract must implement `mt_on_transfer` according to the +// standard. If it does not, MT contract's `mt_resolve_transfer` MUST deal +// with the resulting failed cross-contract call and roll back the transfer. +// * Contract MUST implement the behavior described in `mt_resolve_transfer` +// * `approval_id` is for use with Approval Management extension, see +// that document for full explanation. +// * If using Approval Management, contract MUST nullify approved accounts on +// successful transfer. +// +// Arguments: +// * `receiver_id`: the valid NEAR account receiving the token. +// * `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. +// * `approval_id`: 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. +// * `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, + approval_id: number|null, + memo: string|null, + msg: string, +): Promise {} + + +// Returns `true` if the tokens were transferred from the sender's account. + +// Transfer tokens and call a method on a receiver contract. A successful +// workflow will end in a success execution outcome to the callback on the NFT +// contract at the method `mt_resolve_transfer`. +// +// You can think of this as being similar to attaching native NEAR tokens to a +// function call. It allows you to attach any Multi Token, token in a call to a +// receiver contract. +// +// Requirements: +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security +// purposes +// * Caller must have greater than or equal to the `amount` being requested +// * Contract MUST panic if called by someone other than token owner or, +// if using Approval Management, one of the approved accounts +// * The receiving contract must implement `mt_on_transfer` according to the +// standard. If it does not, MT contract's `mt_resolve_transfer` MUST deal +// with the resulting failed cross-contract call and roll back the transfer. +// * Contract MUST implement the behavior described in `mt_resolve_transfer` +// * `approval_id` is for use with Approval Management extension, see +// that document for full explanation. +// * If using Approval Management, contract MUST nullify approved accounts on +// successful transfer. +// * Contract MUST panic if called with the length of `token_ids` not equal to `amounts` is not equal +// * Contract MUST panic if `approval_ids` is not `null` and does not equal the length of `token_ids` +// +// Arguments: +// * `receiver_id`: the valid NEAR account receiving the token. +// * `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. +// * `approval_ids`: expected approval IDs per `token_ids`. If a `token_id` does +// not have a corresponding approval id then the entry in the array must be marked null. +// The `approval_ids` are numbers smaller than 2^53, and therefore representable as JSON. +// ApprovalId See Approval Management standard for full explanation. +// * `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[], + approval_ids: (number|null)[] | null, + memo: string|null, + msg: string, +): Promise {} + +/****************/ +/* VIEW METHODS */ +/****************/ +// Returns the balance of an account for the given `token_id`. +// The balance though wrapped in quotes and treated like a string, +// the number will be stored as an unsigned integer with 128 bits. +// Arguments: +// * `account_id`: the NEAR account that owns the token. +// * `token_id`: the token to retreive the balance from +function mt_balance_of(account_id: string, token_id: string): string + +// Returns the balances of an account for the given `token_ids`. +// The balances though wrapped in quotes and treated like strings, +// the numbers will be stored as an unsigned integer with 128 bits. +// Arguments: +// * `account_id`: the NEAR account that owns the tokens. +// * `token_ids`: the tokens to retreive the balance from +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. +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. +function mt_batch_supply(token_ids: string[]): (string | null)[] +``` + +The following behavior is required, but contract authors may name this function something other than the conventional `mt_resolve_transfer` used here. + +```ts +// Finalize an `mt_transfer_call` or `mt_batch_transfer_call` chain of cross-contract calls. Generically +// referred to as `mt_transfer_call` as it applies to `mt_batch_transfer_call` as well. +// +// The `mt_transfer_call` process: +// +// 1. Sender calls `mt_transfer_call` on NFT contract +// 2. MT contract transfers token from sender to receiver +// 3. MT contract calls `mt_on_transfer` on receiver contract +// 4+. [receiver contract may make other cross-contract calls] +// N. MT contract resolves promise chain with `mt_resolve_transfer`, and may +// transfer token back to sender +// +// Requirements: +// * Contract MUST forbid calls to this function by any account except self +// * If promise chain failed, contract MUST revert token transfer +// * If promise chain resolves with `true`, contract MUST return token to +// `sender_id` +// +// Arguments: +// * `sender_id`: the sender of `mt_transfer_call` +// * `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` +// * `approved_token_ids`: if using Approval Management, contract MUST provide +// set of original approved accounts in this argument, and restore these +// approved accounts in case of revert. +// +// Returns total amount spent by the `sender_id`, corresonding to the `token_id`. +// The amounts returned, though wrapped in quotes and treated like strings, +// the numbers will be stored as an unsigned integer with 128 bits. +// 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, + receiver_id: string, + token_ids: string[], + approved_account_ids: (null | string[])[] | null, +):string[] {} +``` + +### Receiver Interface + +Contracts which want to make use of `mt_transfer_call` and `mt_batch_transfer_call` must implement the following: + +```ts +// Take some action after receiving a multi token +// +// Requirements: +// * Contract MUST restrict calls to this function to a set of whitelisted +// contracts +// * Contract MUST panic if `token_ids` length does not equals `amounts` +// length +// * Contract MUST panic if `previous_owner_ids` length does not equals `token_ids` +// length +// +// Arguments: +// * `sender_id`: the sender of `mt_transfer_call` +// * `previous_owner_ids`: the account that owned the tokens prior to it being +// transfered to this contract, which can differ from `sender_id` if using +// Approval Management extension +// * `token_ids`: the `token_ids` argument given to `mt_transfer_call` +// * `amounts`: the `token_ids` argument given to `mt_transfer_call` +// * `msg`: information necessary for this contract to know how to process the +// request. This may include method names and/or arguments. +// +// Returns the number of unused tokens in string form. For instance, if `amounts` +// 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[], + token_ids: string[], + amounts: string[], + msg: string, +): Promise; +``` + + [ERC-721]: https://eips.ethereum.org/EIPS/eip-721 + [ERC-1155]: https://eips.ethereum.org/EIPS/eip-1155 + [storage staking]: https://docs.near.org/docs/concepts/storage-staking + [gas]: https://docs.near.org/docs/concepts/gas + [Metadata]: Metadata.md + [NFT]: ../NonFungibleToken/Core.md + [Approval Management]: ApprovalManagement.md + From 4a6c164b507302a7125c8ed1bed537d74c9ee13c Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 28 Sep 2021 15:50:30 -0700 Subject: [PATCH 062/108] chore: fix typos and lint --- specs/Standards/MultiToken/ApprovalManagement.md | 4 ++-- specs/Standards/MultiToken/Metadata.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index 52c759db5..c5c703128 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -27,11 +27,11 @@ Let's consider some examples. Our cast of characters & apps: * Alice: has account `alice` with no contract deployed to it * Bob: has account `bob` with no contract deployed to it -* MT: a contract with account `mt`, implementing only the [Multi Token Standard](README.md) with this Approval Management extension +* MT: a contract with account `mt`, implementing only the [Multi Token Standard](Core.md) with this Approval Management extension * Market: a contract with account `market` which sells tokens from `mt` as well as other token contracts * Bazaar: similar to Market, but implemented differently (spoiler alert: has no `mt_on_approve` function!), has account `bazaar` -Alice and Bob are already [registered](README.md) with MT, Market, and Bazaar, and Alice owns a token on the MT contract with ID=`"1"` and a fungible style token with ID =`"2"` and AMOUNT =`"100"`. +Alice and Bob are already [registered](Core.md) with MT, Market, and Bazaar, and Alice owns a token on the MT contract with ID=`"1"` and a fungible style token with ID =`"2"` and AMOUNT =`"100"`. Let's examine the technical calls through the following scenarios: diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index dfbd70bb2..98db20a21 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -80,6 +80,7 @@ A new attribute MUST be added to each `Token` struct: ```diff type Token = { id: string, + amount: string, + metadata: MTTokenMetadata, + base_metadata_id: string, } From e583a5b4e1fa1af5275d3615363444a1430aae25 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 28 Sep 2021 19:01:46 -0700 Subject: [PATCH 063/108] chore: lint and fix typos --- .../MultiToken/ApprovalManagement.md | 100 ++++++++++-------- 1 file changed, 56 insertions(+), 44 deletions(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index c5c703128..cdff1255b 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -1,4 +1,5 @@ -# Multi Token Standard Approval Management +# Multi Token Standard Approval Management([NEP-246](https://github.com/near/NEPs/discussions/246)) + Version `1.0.0` @@ -31,7 +32,7 @@ Let's consider some examples. Our cast of characters & apps: * Market: a contract with account `market` which sells tokens from `mt` as well as other token contracts * Bazaar: similar to Market, but implemented differently (spoiler alert: has no `mt_on_approve` function!), has account `bazaar` -Alice and Bob are already [registered](Core.md) with MT, Market, and Bazaar, and Alice owns a token on the MT contract with ID=`"1"` and a fungible style token with ID =`"2"` and AMOUNT =`"100"`. +Alice and Bob are already [registered](../StorageManagement.md) with MT, Market, and Bazaar, and Alice owns a token on the MT contract with ID=`"1"` and a fungible style token with ID =`"2"` and AMOUNT =`"100"`. Let's examine the technical calls through the following scenarios: @@ -45,7 +46,7 @@ Let's examine the technical calls through the following scenarios: ### 1. Simple Approval -Alice approves Bob to transfer her token. +Alice approves Bob to transfer her tokens. **High-level explanation** @@ -55,10 +56,10 @@ Alice approves Bob to transfer her token. **Technical calls** -1. Alice calls `mt::mt_approve({ "token_ids": ["1","2"], amounts:[1,100],"account_id": "bob" })`. She attaches 1 yoctoⓃ, (.000000000000000000000001Ⓝ). Using [NEAR CLI](https://docs.near.org/docs/tools/near-cli) to make this call, the command would be: +1. Alice calls `mt::mt_approve({ "token_ids": ["1","2"], amounts:["1","100"], "account_id": "bob" })`. She attaches 1 yoctoⓃ, (.000000000000000000000001Ⓝ). Using [NEAR CLI](https://docs.near.org/docs/tools/near-cli) to make this call, the command would be: near call mt mt_approve \ - '{ "token_id": "1", "account_id": "bob" }' \ + '{ "token_ids": ["1","2"], amounts: ["1","100"], "account_id": "bob" }' \ --accountId alice --amount .000000000000000000000001 The response: @@ -76,11 +77,11 @@ Alice approves Bob to transfer her token. "ids": ["1", "2"] "owner_id": "alice.near", "approvals": [{ - "bob": 1, + "bob": 4, "amount": 1, }, { - "bob": 2, + "bob": 5, "amount": 100, }] } @@ -95,7 +96,7 @@ Alice approves Bob to transfer her token. ### 3. Approval with cross-contract call -Alice approves Market to transfer one of her tokens and passes `msg` so that MT will call `mt_on_approve` on Market's contract. She probably does this via Market's frontend app which would know how to construct `msg` in a useful way. +Alice approves Market to transfer some of her tokens and passes `msg` so that MT will call `mt_on_approve` on Market's contract. She probably does this via Market's frontend app which would know how to construct `msg` in a useful way. **High-level explanation** @@ -122,11 +123,11 @@ Alice approves Market to transfer one of her tokens and passes `msg` so that MT "token_id": ["1","2"], "amounts": ["1","100"], "owner_id": "alice", - "approval_id": 2, + "approval_ids": ["4","5"], "msg": "{\"action\": \"list\", \"price\": [\"100\",\"50\"], \"token\": \"nDAI\" }" }' --accountId mt -3. `market` now knows that it can sell Alice's tokens for 100 [nDAI] and 50 [nDAI] (https://explorer.mainnet.near.org/accounts/6b175474e89094c44da98b954eedeac495271d0f.factory.bridge.near), and that when it transfers it to a buyer using `mt_batch_transfer`, it can pass along the given `approval_id` to ensure that Alice hasn't changed her mind. It can schedule any further cross-contract calls it wants, and if it returns these promises correctly, Alice's initial near-cli call will resolve with the outcome from the final step in the chain. If Alice actually made this call from a Market frontend, the frontend can use this return value for something useful. +3. `market` now knows that it can sell Alice's tokens for 100 [nDAI](https://explorer.mainnet.near.org/accounts/6b175474e89094c44da98b954eedeac495271d0f.factory.bridge.near) and 50 [nDAI](https://explorer.mainnet.near.org/accounts/6b175474e89094c44da98b954eedeac495271d0f.factory.bridge.near), and that when it transfers it to a buyer using `mt_batch_transfer`, it can pass along the given `approval_ids` to ensure that Alice hasn't changed her mind. It can schedule any further cross-contract calls it wants, and if it returns these promises correctly, Alice's initial near-cli call will resolve with the outcome from the final step in the chain. If Alice actually made this call from a Market frontend, the frontend can use this return value for something useful. ### 3. Approval with cross-contract call, edge case @@ -158,7 +159,7 @@ Not to worry, though, she checks `mt_is_approved` and sees that she did successf "token_ids": ["1"], "amounts": ["1000"], "owner_id": "alice", - "approval_id": 3, + "approval_ids": [3], "msg": "{\"action\": \"list\", \"price\": \"100\", \"token\": \"nDAI\" }" }' --accountId mt @@ -198,10 +199,6 @@ Bob transfers same token back to Alice, Alice re-approves Market & Bazaar, listi Bob signs some transaction which results in the `bazaar` contract calling `mt_transfer` on the `mt` contract, as described above. To be trustworthy and pass security audits, `bazaar` needs to pass along `approval_id` so that it knows it has up-to-date information. It does not have up-to-date information, so the call fails. If the initial `mt_transfer` call is part of a call chain originating from a call to `ft_transfer_call` on a fungible token, Bob's payment will be refunded and no assets will change hands. -Note that approval all has different semantics, with approval all -the approval_id is linked to the owners entire state of the contract -as such approval all is vunerable to cross listing discrepencies. - **Technical calls** Using near-cli notation for consistency: @@ -222,7 +219,7 @@ Using near-cli: near call mt mt_revoke '{ "account_id": "market", - "token_ids": ["1", "2"], + "token_ids": ["1"], }' --accountId alice --amount .000000000000000000000001 Note that `market` will not get a cross-contract call in this case. The implementors of the Market app should implement [cron](https://en.wikipedia.org/wiki/Cron)-type functionality to intermittently check that Market still has the access they expect. @@ -244,13 +241,14 @@ Again, note that no previous approvers will get cross-contract calls in this cas ## Reference-level explanation -The `Token` structure returned by `mt_token` must include an `approvals` field, which is a map of account IDs to approval IDs. Using TypeScript's [Record type](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype) notation: +The `Token` structure returned by `mt_token` must include an `approvals` field, which is a map of account IDs to `Approval`. The `amount` field though wrapped in quotes and treated like strings, the number will be stored as an unsigned integer with 128 bits. + in approval is Using TypeScript's [Record type](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype) notation: ```diff - interface Approval = { - amount: number - approval_id: string - } ++ type Approval = { ++ amount: string ++ approval_id: string ++ } type Token = { id: string, owner_id: string, @@ -263,14 +261,13 @@ Example token data: ```json { "id": "1", - "owner_id": "alice.near", "approvals": { "bob.near": { - "amount": 100, + "amount": "100", "approval_id":1, }, "carol.near": { - "amount":2, + "amount":"2", "approval_id": 2, } } @@ -289,7 +286,7 @@ This is a unique number given to each approval that allows well-intentioned mark Note that while this describes an honest mistake, the possibility of such a bug can also be taken advantage of by malicious parties via [front-running](https://defi.cx/front-running-ethereum/). -To avoid this possibility, the MT contract generates a unique approval ID each time it approves an account. Then when calling `mt_transfer` or `mt_transfer_call`, the approved account passes `approval_id` with this value to make sure the underlying state of the token hasn't changed from what the approved account expects. +To avoid this possibility, the MT contract generates a unique approval ID each time it approves an account. Then when calling `mt_transfer`, `mt_transfer_call`, `mt_batch_transfer`, or `mt_batch_transfer_call` the approved account passes `approval_id` or `approval_ids` with this value to make sure the underlying state of the token(s) hasn't changed from what the approved account expects. Keeping with the example above, say the initial approval of the second marketplace generated the following `approvals` data: @@ -328,7 +325,7 @@ The marketplace then tries to call `mt_transfer`, passing outdated information: ```bash # oops! -near call mt-contract.near mt_transfer '{ "approval_id": 2 }' +near call mt-contract.near mt_transfer '{"account_id": "someacct", "amount":"50", "approval_id": 2 }' ``` @@ -353,20 +350,23 @@ The MT contract must implement the following methods: // 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 `nft_on_approve` on `account_id`. See -// `nft_on_approve` description below for details. +// 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 `approvals` -// * `amounts`: the corresponding token_id amounts to add to `approvals` +// * `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: Array, - amounts:Array, + token_ids: [string], + amounts: [string], account_id: string, msg: string|null, ): void|Promise {} @@ -384,7 +384,7 @@ function mt_approve( // * `token_ids`: the token for which to revoke approvals // * `account_id`: the account to remove from `approvals` function mt_revoke( - token_ids: Array, + token_ids: [string], account_id: string ) {} @@ -399,7 +399,7 @@ function mt_revoke( // // Arguments: // * `token_ids`: the token ids with approvals to revoke -function mt_revoke_all(token_ids: Array) {} +function mt_revoke_all(token_ids: [string]) {} /****************/ /* VIEW METHODS */ @@ -408,21 +408,30 @@ function mt_revoke_all(token_ids: Array) {} // 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 `approvals` -// * `approval_id`: an optional approval ID to check against current approval ID for given account -// * `amounts`: specify the positionally corresponding amount for the token_id that at least must be approved +// * `approval_ids`: an optional array of approval IDs to check against +// current approval IDs for given account and `token_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. // // Returns: -// if `approval_id` 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 +// if `approval_id` 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: [number], - approval_id: number|null + amounts: [string], + approval_ids: number[]|null ): boolean {} ``` @@ -457,22 +466,25 @@ 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 // * `owner_id`: the owner of the token -// * `amounts`: the amounts 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. // * `approval_id`: the approval ID stored by NFT contract for this approval. -// Expected to be a number within the 2^53 limit representable by JSON. +// 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: [number], + amounts: [string], owner_id: string, - approval_id: number, + 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 NFT contract. +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. From 585dbc9ccb61a949fb84c4ad731ec3495d269291 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 28 Sep 2021 19:06:55 -0700 Subject: [PATCH 064/108] fix: add support for mt_tokens --- specs/Standards/MultiToken/Core.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 6647d78d4..21f7b9510 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -38,10 +38,11 @@ Prior art: ```ts // The base structure that will be returned for a token. If contract is using -// extensions such as Approval Management, Metadata, or other +// extensions such as Approval Management, Enumeration, Metadata, or other // attributes may be included in this structure. type Token = { id: string, + owner_id: string | null } /******************/ @@ -201,8 +202,8 @@ function mt_transfer_call( // * `receiver_id`: the valid NEAR account receiving the token. // * `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. +// like an array of string, although the numbers will be stored as an array of +// unsigned integer with 128 bits. // * `approval_ids`: expected approval IDs per `token_ids`. If a `token_id` does // not have a corresponding approval id then the entry in the array must be marked null. // The `approval_ids` are numbers smaller than 2^53, and therefore representable as JSON. @@ -226,6 +227,10 @@ function mt_batch_transfer_call( /****************/ /* VIEW METHODS */ /****************/ + + +// Returns the tokens with the given `token_ids` or `null` if no such token. +function mt_tokens(token_ids: string[]) (Token | null)[] // Returns the balance of an account for the given `token_id`. // The balance though wrapped in quotes and treated like a string, // the number will be stored as an unsigned integer with 128 bits. From d1c58a3ba858da6739e720a9c3b33a4399ce5cc8 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 28 Sep 2021 19:18:39 -0700 Subject: [PATCH 065/108] chore: fix typo and lint --- specs/Standards/MultiToken/Core.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 21f7b9510..90da347a1 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -4,7 +4,7 @@ Version `1.0.0` ## Summary -A standard interface for a multi token standard that supports fungible, semi-fungible, and tokens of any type, allowing for ownership, transfer, and batch transfer of tokens regardless of specific type. +A standard interface for a multi token standard that supports fungible, semi-fungible,non-fungible, and tokens of any type, allowing for ownership, transfer, and batch transfer of tokens regardless of specific type. ## Motivation @@ -231,6 +231,7 @@ function mt_batch_transfer_call( // Returns the tokens with the given `token_ids` or `null` if no such token. function mt_tokens(token_ids: string[]) (Token | null)[] + // Returns the balance of an account for the given `token_id`. // The balance though wrapped in quotes and treated like a string, // the number will be stored as an unsigned integer with 128 bits. From bf162c5dfa41103b3702dab7bab0501b78a53f71 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 28 Sep 2021 19:30:58 -0700 Subject: [PATCH 066/108] fix: typos and lint --- specs/Standards/MultiToken/Enumeration.md | 18 ++++++------------ specs/Standards/MultiToken/Metadata.md | 8 ++++---- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/specs/Standards/MultiToken/Enumeration.md b/specs/Standards/MultiToken/Enumeration.md index f9cb51329..032f882c8 100644 --- a/specs/Standards/MultiToken/Enumeration.md +++ b/specs/Standards/MultiToken/Enumeration.md @@ -1,4 +1,5 @@ -# Multi Token Enumeration +# Multi Token Enumeration([NEP-246](https://github.com/near/NEPs/discussions/246)) + Version `1.0.0` @@ -15,22 +16,15 @@ While some Multi Token contracts may forego this extension to save [storage] cos Prior art: - [ERC-721]'s enumeration extension -- [NEP-181]'s enumeration extension +- [Non Fungible Token Standard's](../NonFungibleToken/Enumeration.md) enumeration extension ## Interface The contract must implement the following view methods: -```ts // 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. -type MTToken { - id: string - metadata?: { - base_metadata_id: string - token_metadata: MTTokenMetadata - } -} +```ts // Get a list of all tokens // // Arguments: @@ -42,7 +36,7 @@ type MTToken { function mt_tokens( from_index: string|null, // default: "0" limit: number|null, // default: unlimited (could fail due to gas limit) -): MTToken[] {} +): Token[] {} // Get list of all tokens owned by a given account // @@ -57,7 +51,7 @@ 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) -): MTToken[] {} +): Token[] {} ``` The contract must implement the following view methods if using metadata extension: diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index 98db20a21..55b6f8d2c 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -1,4 +1,5 @@ -# Multi Token Metadata +# Multi Token Metadata([NEP-246](https://github.com/near/NEPs/discussions/246)) + Version `1.0.0` @@ -80,8 +81,7 @@ A new attribute MUST be added to each `Token` struct: ```diff type Token = { id: string, - amount: string, -+ metadata: MTTokenMetadata, ++ token_metadata?: MTTokenMetadata, + base_metadata_id: string, } ``` @@ -126,7 +126,7 @@ For `MTAllTokenMetadata`: ### No incurred cost for core NFT behavior -Contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of metadata for calls to `mt_*` methods other than `mt_*metadata*` or `mt_token`. See `near-contract-standards` [implementation using `LazyOption`](https://github.com/near/near-sdk-rs/blob/c2771af7fdfe01a4e8414046752ee16fb0d29d39/examples/fungible-token/ft/src/lib.rs#L71) as a reference example. +Contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of metadata for calls to `mt_*` methods other than `mt_metadata*` or `mt_tokens`. See `near-contract-standards` [implementation using `LazyOption`](https://github.com/near/near-sdk-rs/blob/c2771af7fdfe01a4e8414046752ee16fb0d29d39/examples/fungible-token/ft/src/lib.rs#L71) as a reference example. ## Drawbacks From b290fc8987d71c4af8581c683a02a468d33e5743 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 29 Sep 2021 15:58:04 -0700 Subject: [PATCH 067/108] feat: add event spec for mt --- specs/Standards/MultiToken/Events.md | 155 +++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 specs/Standards/MultiToken/Events.md diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md new file mode 100644 index 000000000..20185d839 --- /dev/null +++ b/specs/Standards/MultiToken/Events.md @@ -0,0 +1,155 @@ +# Multi Token Event([NEP-246](https://github.com/near/NEPs/discussions/246)) + + +Version `1.0.0` + +## Summary + +Standard interfaces for emitting events for a Multi Token Contract. + +## Motivation + +MT driven apps such as marketplaces and videogames perform a few +core actions minting, burning, transferring, and approving tokens +for transfer. + +Each app has their own way of performing these actions and it +is difficult to consistently capture these actions. We codify these +actions as events, in order to capture them from a variety of context +within the MT Contract. This specification enables the events to be +broadly consumed by indexers, developers, and interested systems. + + +Contract implementers are required to emit events for the actions they take. +These actions are `minting`,`burning`,`transferring`, and `approving tokens to be transferred`. + +This enables indexers, and systems to be able to build a consistent view of the contract and take action on the contract without polling. + +Prior Art: +- [ERC-721]'s events +- [ERC-1155]'s events + + +## Interface + + +```ts +// Interface to capture an event. The indexer looks for events prefixed +// with EVENT_JSON. The rest follows the EventLog +type MtEvent = "mt_mint" | "mt_burn" | "mt_transfer" | "mt_approval" + +// Interface for MT contract event data. It is used to emit event data with +// the near standard logging. +// * `EVENT_JSON`: The standard event prefix required to signal consumers about +// the type of log data being emitted. +// * `standard`: name of standard e.g. nep-246 +// * `version`: e.g. 1.0.0 +// * `event`: `mt_mint` | `mt_burn` | `mt_transfer` | `mt_approval` +// * `data`: associate event data +interface MtEventLogData { + EVENT_JSON: { + standard: string, + version: string, + event: MtEvent, + data: MtMintLog[] | MtBurnLog[] | MtTransferLog[] | MtApprovalLog[] + } +} + +// Minting event log. Emitted when a token is minted/created. +// Requirements +// * Contract MUST emit event when minting a token +// Fields +// * Contract token_ids and amounts MUST be the same length +// * `owner_id`: the account receiving the minted token +// * `token_ids`: the tokens minted +// * `amounts`: the number of tokens burned, wrapped in quotes and treated +// like a string, although the numbers will be stored as an unsigned integer +// . array with 128 bits. +interface MtMintLog { + owner_id: string, + token_ids: string[], + amounts: string[], +} + +// Burning event log. Emitted when a token is burned. +// Requirements +// * Contract MUST emit event when minting a token +// Fields +// * Contract token_ids and amounts MUST be the same length +// * `owner_id`: the account receiving the minted token +// * `token_ids`: the tokens to burned +// * `amounts`: the number of tokens burned, wrapped in quotes and treated +// like a string, although the numbers will be stored as an unsigned integer +// . array with 128 bits. +interface MtBurnLog { + owner_id: string, + token_ids: string[] + amounts: string[] +} + +// Transfer event log. Emitted when a token is transfered. +// Requirements +// * Contract MUST emit event when transferring a token +// Fields +// * `sender_id`: the account sending the minted tokens +// * `receiver_id`: the account receving the minted tokens +// * `token_ids`: the tokens to transfer +// * `amounts`: the number of tokens burned, wrapped in quotes and treated +// like a string, although the numbers will be stored as an unsigned integer +// . array with 128 bits. +interface MtTransferLog { + sender_id: string, + receiver_id: string, + token_ids: string[], + amounts: string[] +} + +// Transfer event log. Emitted when a token is transfered. +// Requirements +// * Contract MUST emit event when transferring a token +// * Contract token_ids and amounts MUST be the same length +// Fields +// * `sender_id`: the account sending the minted tokens +// * `receiver_id`: the account receving the minted tokens +// * `token_ids`: the tokens to transfer +// * `amounts`: the number of tokens burned, wrapped in quotes and treated +// like a string, although the numbers will be stored as an unsigned integer +// . array with 128 bits. +interface MtTransferLog { + sender_id: string, + receiver_id: string, + token_ids: string[], + amounts: string[] +} + +// Approval event log. Emitted when a token's approval has changed. +// Requirements +// * Contract MUST emit event when approval of tokens have changed +// * Contract token_ids and amounts MUST be the same length +// Fields +// * `account_id`: the account sending the approval +// * `approved_id`: the account being approved +// * `token_ids`: the tokens to transfer +// * `amounts`: the number of tokens approved for transfer, wrapped in quotes and treated +// like a string, although the numbers will be stored as an unsigned integer +// . array with 128 bits. +interface MtApprovalLog { + account_id: string, + approved_id: string, + token_ids: string[], + amounts: string[] +} + +``` + +## Drawbacks + +There is a known limitation of 16kb strings when capturing logs. +This can be observed from `token_ids` that may vary in length +for different apps so the amount of logs that can +be executed may vary. + + [ERC-721]: https://eips.ethereum.org/EIPS/eip-721 + [ERC-1155]: https://eips.ethereum.org/EIPS/eip-1155 + [storage]: https://docs.near.org/docs/concepts/storage-staking + From 08e244a573b199711378dfc584d73038a820a7e9 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 29 Sep 2021 16:05:30 -0700 Subject: [PATCH 068/108] fix: refactor README to match other standards --- specs/Standards/MultiToken/README.md | 547 +-------------------------- 1 file changed, 6 insertions(+), 541 deletions(-) diff --git a/specs/Standards/MultiToken/README.md b/specs/Standards/MultiToken/README.md index be25e483f..a593d3907 100644 --- a/specs/Standards/MultiToken/README.md +++ b/specs/Standards/MultiToken/README.md @@ -1,542 +1,7 @@ -- Proposal Name: Multi-Token-Standard -- Start Date: 2021/07/24 -- Issue(s): #245. +## Multi Token Standard -Version `1.0.0` - -# Summary -[summary]: #summary - -A standard interface for a multi token standard that supports fungible, semi-fungible, and tokens of any type, allowing for ownership, transfer, and batch transfer of tokens generally regardless of specific type. - -# Motivation -[motivation]: #motivation -Having a single contract represent both NFTs and FTs can greatly improve efficiency as demonstrated by Enjin Coin. The ability to make batch requests with multiple asset classes can allow operations that currently require _many_ transactions to be completed in a single transaction that can transfer both NFTs and FTs that are a part of same token contract. - -Having this will also increase NEAR's ability to work interoperably with other chains. This will reduce the complexity required to represent these emerging asset classes. - - -Prior art: -- EIP-1155 : https://github.com/ethereum/EIPs/issues/1155 - -- This NEP derives some examples and format from: https://github.com/near/NEPs/pull/21 - - -- NFT Discussions: -https://github.com/near/NEPs/discussions/171 -https://gov.near.org/t/nft-standard-discussion/853 - -Discussions out of band: - - https://gov.near.org/t/multi-token-standard-discussion/2917 - - https://github.com/shipsgold/multi-token-standard-impl/tree/main/meetings - -# Guide-level explanation - -There are a few concepts in the scenarios below: -- **Total supply**. It's the total number of tokens in circulation. -- **Balance owner**. An account ID that owns some amount of tokens. -- **Transfer**. Moves some amount from one account to another account. -- **Fungibility**. An indistinguishable amount of tokens to exchange -- **Non Fungibility**. Tokens that are differentiable from each other. - - -[guide-level-explanation]: #guide-level-explanation - -We should be able to do the following: -- Get balance of a single token_id per account per single transaction -- Get balance of multiple token_ids per account per single transaction -- Get total supply of a token by token_id per single transaction -- Get total supply of tokens per token_ids per single transaction -- Represent non fungibility of tokens -- Represent fungibility of tokens -- Transfer tokens by id in batch in a single transaction to a single account -- Use these tokens on an exchange -- Refund storage costs for fungible tokens - -### Real scenarios - -#### Simple transfer - -Alice wants to send 5 `gold` tokens to Bob. - -Let's assume the following: -- The `gold` token is defined in the `games.near` contract with `token_id` of `g133`. -- Alice's account is `alice.near`. -- Bob's account is `bob.near`. -- The precision for `gold` on the `games.near` contract is `10^8`. -- The `amount` to represent 5 `gold` tokens is `5 * 10^8`, or as a number is `500000000`. - -High-level explanation: - -Alice needs to issue one transaction to the `games.near` contract to transfer 5 `gold` tokens (multiplied by the precision defined in `gold` token metadata) to Bob. - -Technical calls: - -1. `alice.near` calls `games.near::mt_transfer({"receiver_id": "bob.near", "amount": "500000000", "token_id": "g133", "memo": "for my dinner"})`. - -#### Simple batch transfer - -Alice wants to send 1 unique (non-fungible) `gemstone`, 5 `gold` (fungible) and 10 `silver` (fungible) tokens to Bob. - -Let's assume the following: -- The unique (non-fungible) `gem` token is defined in the `games.near` contract with `token_id` -`uu2` -- The `gold` token is defined in the `games.near` contract with `token_id` `g133`. -- The `silver` token is defined in the `games.near` contract with `token_id` `s133`. -- Alice's account is `alice.near`. -- Bob's account is `bob.near`. -- The precision for `gold` on the `games.near` contract is `10^8`. -- The precision for `silver` on the `games.near` contract is also `10^8`. -- The `amount` to represent 5 `gold` tokens is `5 * 10^8` or as a number is `500000000`. -- The `amount` to represent 10 `silver` tokens is `10 * 10^8` or as a number is `1000000000`. -- The `amount` to represent 1 `gem` token is `1` or as a number is `1` - -High-level explanation: - -Alice needs to issue one transaction to `games.near` contract to transfer 5 `gold` tokens and 10 `silver` tokens (multiplied by precision) and 1 `gem` to Bob. - -Technical calls: - -1. `alice.near` calls `games.near::mt_transfer_batch({"receiver_id": "bob.near", "amounts": ["500000000", "1000000000", "1"], "token_ids": ["g133", "s133", "uu2"], "memo": "well done"})`. - - - -#### Token deposit to a contract - -Alice wants to deposit `gold` tokens to a compound interest contract to earn some rewards. - -Let's assume the following: -- The `gold` token is represented by the `games.near` contract with `token_id` `g133` . -- Alice's account is `alice.near`. -- The compound interest contract is `compound.near`. -- The precision on `gold` token is `10^18`. -- The `amount` to represent 1000 tokens is `1000 * 10^18` or as a number is `1000000000000000000000`. -- The `compound.near` contract can work with many different token contracts and types. - -High-level explanation: - -Alice needs to issue a single transaction to `games.near` that will internally issue a cross contract call to `compound.near`. - -The initial transaction to `games.near` is made with `compound.near` as the receiver of a set token_ids and amounts from `alice.near`. - -This call then waits on a response from `compound.near`. If `compound.near` responds with failure, the tx is aborted. - -Otherwise `games.near` contract accepts the results and resolves the promise completing the transaction. - -- If transfer succeeded, `compound.near` can increase local ownership for `alice.near` to 1000 for `gold` , whose `token_id` is `g133` - -- If transfer fails, `compound.near` doesn't need to do anything in current example, but maybe can notify `alice.near` of unsuccessful transfer. - -Technical calls: -1. `alice.near` calls `games.near::mt_transfer_call({"receiver_id": "compound.near", amount: "1000000000000000000000", "token_id": "g133", msg: "interest-building"})`. - During the `mt_transfer_call` call, `compound.near` does the following: - fn mt_on_transfer( - &mut self, - sender_id: AccountId, - token_ids: Vec, - amounts: Vec, - approval_ids: Option>, - msg: String, - ) -> PromiseOrValue>; -} - 1. calls `compound::mt_on_transfer({"sender_id": "alice.near", "token_ids":["g133"], "amounts": ["1000000000000000000000"], msg: "interest-building"})`. - 2. `compound.near` resolves the request/fails and `games.near` contract handles the result of the promise, with `games.near::mt_resolve_transfer()` returning refunded amount if there is any or handling follow up from the result of compound cross contract call - -#### Batch Token deposit to a contract - -Alice wants to deposit `silver` (fungible) and `gold` (fungible) tokens and the unique `gem` (non-fungible) to a compound interest contract to earn some rewards. - -Let's assume the following: -- The `gold` token is represented by the `games.near` contract with `token_id` of `g133` . -- The `silver` token is represented by the `games.near` contract with `token_id` of `s133` . -- The `gem` unique only one nft token is represented by the `games.near` contract with `token_id` of `uu2` . -- Alice's account is `alice.near`. -- The compound interest contract is `compound.near`. -- The precision on `gold` token is `10^18`. -- The precision on `silver` token is `10^18`. -- The `amount` used to represent 1000 tokens is `1000 * 10^18` or as a number is `1000000000000000000000`. -- The `compound.near` contract can work with many different token contracts and types. - -High-level explanation: - -Alice needs to issue a single transaction to `games.near` that will internally issue a cross contract call to `compound.near`. - -The initial transaction to `games.near` is made with `compound.near` as the receiver of a set token_ids and amounts from `alice`. - -This call then waits on a response from `compound.near`. If `compound.near` responds with failure, the tx is aborted. - -Otherwise `games.near` contract accepts the results and resolves the promise completing the transaction. - -- If transfer succeeded, `compound.near` can increase local ownership for `alice.near` to 1000 for `gold` with token_id `g133` - -- If transfer fails, `compound.near` doesn't need to do anything in current example, but maybe can notify `alice.near` of unsuccessful transfer. - -Technical calls: -1. `alice.near` calls `games.near::mt_transfer_batch_call({"receiver_id": "compound.near", amounts: ["1000000000000000000000","1000000000000000000000", "1"], "token_ids": ["g133","s133","uu2"], msg: "interest-building"})`. - During the `mt_transfer_call` call, `compound.near` does the following: - fn mt_on_transfer( - &mut self, - sender_id: AccountId, - token_ids: Vec, - amounts: Vec, - previous_owner_id: AccountId, - msg: String, - ) -> PromiseOrValue>; -} - 1. calls `compound.near::mt_on_transfer({"sender_id": "alice.near", amounts: ["1000000000000000000000","1000000000000000000000", "1"], "token_ids": ["g133","s133","uu2"], msg: "interest-building"})` - 2. `compound.near` resolves the request/fails and `games.near` contract handles response from the promise with `games.near::mt_resolve_transfer` returning refunded amount if there is any or handling follow up from the result of compound cross contract call - -# Reference-level explanation -[reference-level-explanation]: #reference-level-explanation -WIP implementation: https://github.com/shipsgold/multi-token-standard-impl/tree/feat/initial-token -### Core Trait -``` -// The base structure that will be returned for a token. If contract is using -// extensions such as Approval Management, Metadata, or other -// attributes may be included in this structure. -pub trait Token { - id: string, -} - -pub trait MultiTokenCore { - /// Basic token transfer. Transfer a token or tokens given a token_id. The token id can correspond to - /// either a NonFungibleToken or FungibleToken - this is differentiated by the implementation. - /// - /// Requirements: - /// * Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes - /// * Contract MUST panic if called by someone other than token owner or, - /// * If using Approval Management, contract MUST nullify approved accounts on - /// successful transfer. - /// * TODO: needed? Both accounts must be registered with the contract for transfer to - /// succeed. See see https://nomicon.io/Standards/StorageManagement.html - /// - /// Arguments: - /// * `receiver_id`: the valid NEAR account receiving the token - /// * `token_id`: the token or tokens to transfer - /// * `amount`: the token amount of tokens to transfer for token_id - /// * `approval_id`: 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 - fn mt_transfer( - &mut self, - receiver_id: AccountId, - token_id: TokenId, - amount: U128, - approval_id: Option, - memo: Option, - ); - - /// Transfer token/s and call a method on a receiver contract. A successful - /// workflow will end in a success execution outcome to the callback on the MultiToken - /// contract at the method `mt_resolve_transfer`. - /// - /// You can think of this as being similar to attaching NEAR tokens as a `deposit` to a - /// function call. It allows you to attach any FungibleToken or NonFungibleToken in a call to a - /// receiver contract. - /// - /// Requirements: - /// * Caller of the method must attach a deposit of 1 yoctoⓃ for security - /// purposes - /// * Contract MUST panic if called by someone other than token owner or, - /// if using Approval Management, one of the approved accounts - /// * The receiving contract must implement `mt_on_transfer` according to the - /// standard. If it does not, MultiToken contract's `mt_resolve_transfer` MUST deal - /// with the resulting failed cross-contract call and roll back the transfer. - /// * Contract MUST implement the behavior described in `mt_resolve_transfer` - /// - /// Arguments: - /// * `receiver_id`: the valid NEAR account receiving the token. - /// * `token_id`: the token to send. - /// * `amount`: amount of tokens to transfer for token_id - /// * `approval_id`: 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. - /// * `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. - fn mt_transfer_call( - &mut self, - receiver_id: AccountId, - token_id: TokenId, - amount: U128, - approval_id: Option, - memo: Option, - msg: String, - ) -> PromiseOrValue; - - /// Batch token transfer. Transfer a tokens given token_ids and amounts. The token ids can correspond to - /// either Non-Fungible Tokens or Fungible Tokens or some combination of the two. The token ids - /// are used to segment the types on a per contract implementation basis. - /// - /// Requirements - /// * Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes - /// * Contract MUST panic if called by someone other than token owner or, - /// if using Approval Management, one of the approved accounts - /// * `approval_id` is for use with Approval Management, - /// see https://nomicon.io/Standards/NonFungibleToken/ApprovalManagement.html - /// * If using Approval Management, contract MUST nullify approved accounts on - /// successful transfer. - /// * TODO: needed? Both accounts must be registered with the contract for transfer to - /// succeed. See see https://nomicon.io/Standards/StorageManagement.html - /// * The token_ids vec and amounts vec must be of equal length and equate to a 1-1 mapping - /// between amount and id. In the event that they do not line up the call should fail - /// - /// Arguments: - /// * `receiver_id`: the valid NEAR account receiving the token - /// * `token_ids`: the tokens to transfer - /// * `amounts`: the amount of tokens to transfer for corresponding token_id - /// * `approval_ids`: expected approval ID. A number smaller than - /// 2^53, and therefore representable as JSON. See Approval Management - /// standard for full explanation. Must have same length and order as /// token_ids - /// * `memo` (optional): for use cases that may benefit from indexing or - /// providing information for a transfer - - fn mt_batch_transfer( - &mut self, - receiver_id: AccountId, - token_ids: Vec, - amounts: Vec, - approval_ids: Option>>, - memo: Option, - ); - /// Batch transfer token/s and call a method on a receiver contract. A successful - /// workflow will end in a success execution outcome to the callback on the MultiToken - /// contract at the method `mt_resolve_batch_transfer`. - /// - /// You can think of this as being similar to attaching NEAR tokens as a `deposit` to a - /// function call. It allows you to attach any Fungible or Non Fungible Token in a call to a - /// receiver contract. - /// - /// Requirements: - /// * Caller of the method must attach a deposit of 1 yoctoⓃ for security - /// purposes - /// * Contract MUST panic if called by someone other than token owner or, - /// if using Approval Management, one of the approved accounts - /// * The receiving contract must implement `mt_on_transfer` according to the - /// standard. If it does not, MultiToken contract's `mt_resolve_batch_transfer` MUST deal - /// with the resulting failed cross-contract call and roll back the transfer. - /// * Contract MUST implement the behavior described in `mt_resolve_batch_transfer` - /// * `approval_id` is for use with Approval Management extension, see - /// that document for full explanation. - /// * If using Approval Management, contract MUST nullify approved accounts on - /// successful transfer. - /// - /// Arguments: - /// * `receiver_id`: the valid NEAR account receiving the token. - /// * `token_ids`: the tokens to transfer - /// * `amounts`: the amount of tokens to transfer for corresponding token_id - /// * `approval_ids`: expected approval ID. A number smaller than - /// 2^53, and therefore representable as JSON. See Approval Management - /// standard for full explanation. Must have same length and order as /// token_ids - /// standard for full explanation. Must have same length as token_ids - /// * `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. - - fn mt_batch_transfer_call( - &mut self, - receiver_id: AccountId, - token_ids: Vec, - amounts: Vec, - approval_ids: Option>>, - memo: Option, - msg: String, - ) -> PromiseOrValue>; - - /// Get the balance of an account for the given `token_id`. For fungible token returns back amount, for - /// non fungible token it returns back constant 1. - fn mt_balance_of(&self, owner_id: AccountId, token_id: TokenId) -> U128; - - /// Get the balances of an an account given token_ids. For fungible token returns back amount, for - /// non fungible token it returns back constant 1. returns vector of balances corresponding to token_ids - /// in a 1-1 mapping - fn mt_balance_of_batch(&self, owner_id: AccountId, token_ids: Vec) -> Vec; - - /// Returns the total supply of the token in a decimal string representation given token_id. - fn mt_total_supply(&self, token_id: TokenId) -> U128; - - // Returns the total supplies of the tokens given by token_ids in a decimal string representation. - fn mt_total_supply_batch(&self, token_ids: Vec) -> Vec; -} -``` -### Receiver Trait -#### Notes -- TokenId is of type String -``` -pub trait MultiTokenReceiver { - /// Take some action after receiving a MultiToken-tokens token - /// - /// Requirements: - /// * Contract MUST restrict calls to this function to a set of whitelisted MultiToken - /// contracts - /// - /// Arguments: - /// * `sender_id`: the sender of `mt_transfer_call` - /// * `previous_owner_id`: the account that owned the tokens prior to it being - /// transferred to this contract, which can differ from `sender_id` if using - /// Approval Management extension - /// * `token_ids`: the `token_ids` argument given to `mt_transfer_call` - /// * `msg`: information necessary for this contract to know how to process the - /// request. This may include method names and/or arguments. - /// - // Returns a value, or a promise which resolves with a vector of values. The values are the - /// number of unused tokens in string form. For instance, if `amount` is 10 but only 9 are - /// needed, it will return ["1"]. - - fn mt_on_transfer( - &mut self, - sender_id: AccountId, - previous_owner_id: AccountId, - token_ids: Vec, - amounts: Vec, - msg: String, - ) -> PromiseOrValue>; -} -``` -### Resolver Trait -#### Notes -- TokenId is of type String -``` -/// Used when MultiTokens are transferred using `mt_transfer_call`. This is the method that's called after `mt_on_transfer`. This trait is implemented on the MultiToken contract. -pub trait MultiTokenResolver { - /// Finalize an `mt_transfer_call` chain of cross-contract calls. - /// - /// The `mt_transfer_call` process: - /// - /// 1. Sender calls `mt_transfer_call` on MultiToken contract - /// 2. MultiToken contract transfers token from sender to receiver - /// 3. MultiToken contract calls `mt_on_transfer` on receiver contract - /// 4+. [receiver contract may make other cross-contract calls] - /// N. MultiToken contract resolves promise chain with `mt_resolve_transfer`, and may - /// transfer token back to sender - /// - /// Requirements: - /// * Contract MUST forbid calls to this function by any account except self - /// * If promise chain failed, contract MUST revert token transfer - /// * If promise chain resolves with `true`, contract MUST return token to - /// `sender_id` - /// - /// Arguments: - /// * `previous_owner_id`: the owner prior to the call to `mt_transfer_call` - /// * `receiver_id`: the `receiver_id` argument given to `mt_transfer_call` - /// * `token_ids`: the `token_ids` argument given to `mt_transfer_call` - /// * `approved_account_ids`: if using Approval Management, contract MUST provide - /// set of original approved accounts in this argument, and restore these - /// approved accounts in case of revert. In this case it may be multiple sets of approvals . If specified the length and order must correspond to token_ids - - /// Returns the amount of how many total tokens were spent by `sender_id`, corresponding to the `token_id`. - fn mt_resolve_transfer( - &mut self, - sender_id: AccountId, - receiver_id: AccountId, - token_ids: Vec, - amounts: Vec, - approved_account_ids: Option>>>, - ) -> Vec; -} -``` -### Storage Management Trait -``` -pub trait StorageManagement { - // if `registration_only=true` MUST refund above the minimum balance if the account didn't exist and - // refund full deposit if the account exists. - fn mt_storage_deposit( - &mut self, - token_ids: Vec, - account_id: Option, - registration_only: Option, - ) -> StorageBalance; - - /// Withdraw specified amount of available Ⓝ for predecessor account. - /// - /// This method is safe to call. It MUST NOT remove data. - /// - /// `amount` is sent as a string representing an unsigned 128-bit integer. If - /// omitted, contract MUST refund full `available` balance. If `amount` exceeds - /// predecessor account's available balance, contract MUST panic. - /// - /// If predecessor account not registered, contract MUST panic. - /// - /// MUST require exactly 1 yoctoNEAR attached balance to prevent restricted - /// function-call access-key call (UX wallet security) - /// - /// Returns the StorageBalance structure showing updated balances. - fn storage_withdraw(&mut self, token_ids:Vec, amount: Option) -> StorageBalance; - - /// Unregisters the predecessor account and returns the storage NEAR deposit back. - /// - /// If the predecessor account is not registered, the function MUST return `false` without panic. - /// - /// If `force=true` the function SHOULD ignore account balances (burn them) and close the account. - /// Otherwise, MUST panic if caller has a positive registered balance (eg token holdings) or - /// the contract doesn't support force unregistration. - /// MUST require exactly 1 yoctoNEAR attached balance to prevent restricted function-call access-key call - /// (UX wallet security) - /// Returns `true` if the account was successfully unregistered by this call. - /// Returns `false` if account was already unregistered. - fn storage_unregister(&mut self, token_ids:Vec, force: Option) -> Vec; - - /****************/ - /* VIEW METHODS */ - /****************/ - // Returns minimum and maximum allowed balance amounts to interact with this - // contract. See StorageBalanceBounds. - fn storage_balance_bounds(&self, token_id:Vec, account_id: Option) -> StorageBalanceBounds; - - // If `account_id` is not registered, must return `null`. - fn storage_balance_of(&self, token_ids:Vec, account_id: AccountId) -> Option; -} -``` - -# Drawbacks -[drawbacks]: #drawbacks -Doing this adds another spec and codebase to the standards. It could be seen that we could leave this to developers to implement custom solutions and have them create a contract that implements both `NEP-141` and `NEP-171` methods together. There is some additional complexity -in ux, when considering batch size request and gas limitations, that might trip some developers up. - -# Rationale and alternatives -[rationale-and-alternatives]: #rationale-and-alternatives -The rationale for this design, is that we want to support developers that need batch requests. In many ecosystems, a single NFT or -series of NFT are not sufficient for representing and managing potentially 100s of tokens. Managing cross contract calls and the token contracts themselves become a huge burden on the developer. This will reduce the complexity, and allow developers to easily bridge over to other contracts like ERC-1155 on other chains that allow for a this style of representation. - -In the design phase it was considered to simply rely on the underlying implementations of fungible tokens and non-fungible tokens -to be the scope of interaction with the chain. Doing this we would have tied the implementations to the bounds of FT and NFT. By loosing this up a bit, we are able to be a bit more flexible in what's possible. - -Not doing this means we really won't have a great way of supporting use cases where developers need to represent and manage -large varying quantities and types of tokens. They will all have to implement it on their own, and that would make for a fragmented -and weak ecosystem. Where every developer would not be able to reliably trade these assets. - -# Unresolved questions -[unresolved-questions]: #unresolved-questions -The unresolved questions, are really what type of metadata , is required for this spec? -- We decided that having metadata on chain is good, and that at later time we can have a spec for extra data if needed - -Can we represent events in this spec, which would improve the ecosystem quite a bit? If we represent events what should those events be? -- We decided events aren't here yet in the ecosystem, in the way we' like them to be so they are't apart of this standard - -Should we have a spec for TokenType? -- We decided TokenType shouldn't be exposed for the public interface consumption. We will leave this description in the hands of the implementers - -Should we have a spec for offchain metadata? -- We decided no spec for offchain metadata yet but maybe in the future - -Does the current storage management scheme work for people? -- The current storage management scheme works for folks - -How freeform should this token be? Right now there is a notion of supply, which is not 100% guaranteed every token has -or wants to track supply semantics. Not having supply makes everything more difficult and requires consumers of the contract -to track minting and burning events. -- This point was taken to show that due to lack of proper events, and having the additional capability of storing this on chain, would -result in a reduction of complexity for consumers of the contract data. - -Approval Management is probably out of the scope of this solution. - - -# Future possibilities -[future-possibilities]: #future-possibilities -Future possibilities could be around enumeration extension like for the NFT spec, an off chain data spec, approval management if it's required, and error status codes. +- [Multi Token Core](Core.md) +- [Multi Token Metadata](Metadata.md) +- [Multi Token Approval Management](ApprovalManagement.md) +- [Multi Token Enumeration](Enumeration.md) +- [Multi Token Events](Events.md) From 6fdf26c8f8ded3781df71bafbfeb2e38d0f8bec6 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 30 Sep 2021 20:23:06 -0700 Subject: [PATCH 069/108] chore: lint, tidy, and typo correction to core Co-authored-by: Mike Purvis --- specs/Standards/MultiToken/Core.md | 28 +++++++++++++------------- specs/Standards/MultiToken/Events.md | 4 ++-- specs/Standards/MultiToken/Metadata.md | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 90da347a1..0f19da249 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -9,14 +9,14 @@ A standard interface for a multi token standard that supports fungible, semi-fun ## Motivation -In the three years since [ERC-1155] was ratified by the Ethereum Community, Multi Token based contracts have proven themselves valuable assets. Many blockchain projects emulate this standard for representing multiple token assets classes in a single contract. The ability to reduce transaction overhead for marketplaces, video games, daos, and exchanges is appealing to the blockchain ecosystem and simplifies transactions for developers. +In the three years since [ERC-1155] was ratified by the Ethereum Community, Multi Token based contracts have proven themselves valuable assets. Many blockchain projects emulate this standard for representing multiple token assets classes in a single contract. The ability to reduce transaction overhead for marketplaces, video games, DAOs, and exchanges is appealing to the blockchain ecosystem and simplifies transactions for developers. -Having a single contract represent NFTs, FTs, and tokens that sit inbetween greatly improves efficiency. The standard also introdued the ability to make batch requests with multiple asset classes reducing complexity. This standard allows operations that currently require _many_ transactions to be completed in a single transaction that can transfer not only NFTs and FTs, but any tokens that are a part of same token contract. +Having a single contract represent NFTs, FTs, and tokens that sit inbetween greatly improves efficiency. The standard also introduced the ability to make batch requests with multiple asset classes reducing complexity. This standard allows operations that currently require _many_ transactions to be completed in a single transaction that can transfer not only NFTs and FTs, but any tokens that are a part of same token contract. -With this standard, we have sought to take advantage of the ability of the NEAR bockchain to scale. It's sharded runtime, and [storage staking] model that decouples [gas] fees from storage demand, enables ultra low transaction fees and greater on chain storage ( see [Metadata]extenson). +With this standard, we have sought to take advantage of the ability of the NEAR blockchain to scale. Its sharded runtime, and [storage staking] model that decouples [gas] fees from storage demand, enables ultra low transaction fees and greater on chain storage ( see [Metadata] extension). -With the aformentioned, it is note worthy to mention that like the [NFT] standard the Multi Token standard, implements `mt_transfer_call`, -which allows, a user to attach many tokens to a call to a separate contract. Additionally this standard includes an optional [Approval Management] extension. The extension allows marketplaces to trade on behalf of a user, providing additional flexibility for dApps. +With the aforementioned, it is noteworthy to mention that like the [NFT] standard the Multi Token standard, implements `mt_transfer_call`, +which allows, a user to attach many tokens to a call to a separate contract. Additionally, this standard includes an optional [Approval Management] extension. The extension allows marketplaces to trade on behalf of a user, providing additional flexibility for dApps. Prior art: @@ -30,7 +30,7 @@ Prior art: **NOTES**: - All amounts, balances and allowance are limited by `U128` (max value `2**128 - 1`). - Token standard uses JSON for serialization of arguments and results. -- Amounts in arguments and results have are serialized as Base-10 strings, e.g. `"100"`. This is done to avoid JSON limitation of max integer value of `2**53`. +- Amounts in arguments and results are serialized as Base-10 strings, e.g. `"100"`. This is done to avoid JSON limitation of max integer value of `2**53`. - The contract must track the change in storage when adding to and removing from collections. This is not included in this core multi token standard but instead in the [Storage Standard](../StorageManagement.md). - To prevent the deployed contract from being modified or deleted, it should not have any access keys on its account. @@ -102,12 +102,12 @@ function mt_transfer( // * `receiver_id`: the valid NEAR account receiving the token // * `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 +// like an array of strings, although the numbers will be stored as an array of unsigned integer // with 128 bits. // * `approval_ids`: expected approval IDs per `token_ids`. If a `token_id` does // not have a corresponding approval id then the entry in the array must be marked null. // The `approval_ids` are numbers smaller than 2^53, and therefore representable as JSON. -// ApprovalId See Approval Management standard for full explanation. +// See Approval Management standard for full explanation. // * `memo` (optional): for use cases that may benefit from indexing or // providing information for a transfer @@ -174,7 +174,7 @@ function mt_transfer_call( // Returns `true` if the tokens were transferred from the sender's account. // Transfer tokens and call a method on a receiver contract. A successful -// workflow will end in a success execution outcome to the callback on the NFT +// workflow will end in a success execution outcome to the callback on the MT // contract at the method `mt_resolve_transfer`. // // You can think of this as being similar to attaching native NEAR tokens to a @@ -237,7 +237,7 @@ function mt_tokens(token_ids: string[]) (Token | null)[] // the number will be stored as an unsigned integer with 128 bits. // Arguments: // * `account_id`: the NEAR account that owns the token. -// * `token_id`: the token to retreive the balance from +// * `token_id`: the token to retrieve the balance from function mt_balance_of(account_id: string, token_id: string): string // Returns the balances of an account for the given `token_ids`. @@ -245,7 +245,7 @@ function mt_balance_of(account_id: string, token_id: string): string // the numbers will be stored as an unsigned integer with 128 bits. // Arguments: // * `account_id`: the NEAR account that owns the tokens. -// * `token_ids`: the tokens to retreive the balance from +// * `token_ids`: the tokens to retrieve the balance from 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. @@ -267,7 +267,7 @@ The following behavior is required, but contract authors may name this function // // The `mt_transfer_call` process: // -// 1. Sender calls `mt_transfer_call` on NFT contract +// 1. Sender calls `mt_transfer_call` on MT contract // 2. MT contract transfers token from sender to receiver // 3. MT contract calls `mt_on_transfer` on receiver contract // 4+. [receiver contract may make other cross-contract calls] @@ -289,7 +289,7 @@ The following behavior is required, but contract authors may name this function // set of original approved accounts in this argument, and restore these // approved accounts in case of revert. // -// Returns total amount spent by the `sender_id`, corresonding to the `token_id`. +// Returns total amount spent by the `sender_id`, corresponding to the `token_id`. // The amounts returned, though wrapped in quotes and treated like strings, // the numbers will be stored as an unsigned integer with 128 bits. // Example: if sender_id calls `mt_transfer_call({ "amounts": ["100"], token_ids: ["55"], receiver_id: "games" })`, @@ -323,7 +323,7 @@ Contracts which want to make use of `mt_transfer_call` and `mt_batch_transfer_ca // Arguments: // * `sender_id`: the sender of `mt_transfer_call` // * `previous_owner_ids`: the account that owned the tokens prior to it being -// transfered to this contract, which can differ from `sender_id` if using +// transferred to this contract, which can differ from `sender_id` if using // Approval Management extension // * `token_ids`: the `token_ids` argument given to `mt_transfer_call` // * `amounts`: the `token_ids` argument given to `mt_transfer_call` diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index 20185d839..260267a3b 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -62,7 +62,7 @@ interface MtEventLogData { // * Contract token_ids and amounts MUST be the same length // * `owner_id`: the account receiving the minted token // * `token_ids`: the tokens minted -// * `amounts`: the number of tokens burned, wrapped in quotes and treated +// * `amounts`: the number of tokens minted, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer // . array with 128 bits. interface MtMintLog { @@ -76,7 +76,7 @@ interface MtMintLog { // * Contract MUST emit event when minting a token // Fields // * Contract token_ids and amounts MUST be the same length -// * `owner_id`: the account receiving the minted token +// * `owner_id`: the account whose token(s) are being burned // * `token_ids`: the tokens to burned // * `amounts`: the number of tokens burned, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index 55b6f8d2c..789822033 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -111,7 +111,7 @@ For `MTTokenMetadata`: - `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`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token was issued or minted +- `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`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token expires - `starts_at`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token starts being valid - `updated_at`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token was last updated From 9b4fc8ff292b3325598046d2d9958153b29f5b43 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 30 Sep 2021 20:26:21 -0700 Subject: [PATCH 070/108] chore: fix typos with enumeration Co-authored-by: Mike Purvis --- specs/Standards/MultiToken/Enumeration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/Enumeration.md b/specs/Standards/MultiToken/Enumeration.md index 032f882c8..d891aa1af 100644 --- a/specs/Standards/MultiToken/Enumeration.md +++ b/specs/Standards/MultiToken/Enumeration.md @@ -68,7 +68,7 @@ The contract must implement the following view methods if using metadata extensi function mt_tokens_base_metadata_all( from_index: string | null, limit: number | null - ): BaseTokenMetadata[] + ): MTBaseTokenMetadata[] ``` From 42d0e3ba2bafed8ab3a9ea1e6bdf401f58263869 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 30 Sep 2021 20:35:26 -0700 Subject: [PATCH 071/108] chore: typos, lint, and tidy with approval management Co-authored-by: Mike Purvis --- .../MultiToken/ApprovalManagement.md | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index cdff1255b..aa163d112 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -13,7 +13,7 @@ A system for allowing a set of users or contracts to transfer specific tokens on 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. -NEAR's [core Multi Token standard](README.md) 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 Managament system. +NEAR's [core Multi Token standard](README.md) 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. @@ -32,12 +32,12 @@ Let's consider some examples. Our cast of characters & apps: * Market: a contract with account `market` which sells tokens from `mt` as well as other token contracts * Bazaar: similar to Market, but implemented differently (spoiler alert: has no `mt_on_approve` function!), has account `bazaar` -Alice and Bob are already [registered](../StorageManagement.md) with MT, Market, and Bazaar, and Alice owns a token on the MT contract with ID=`"1"` and a fungible style token with ID =`"2"` and AMOUNT =`"100"`. +Alice and Bob are already [registered](../StorageManagement.md) with MT, Market, and Bazaar, and Alice owns a token on the MT contract with ID=`"1"` and a fungible style token with ID =`"2"` and AMOUNT =`"100"`. Let's examine the technical calls through the following scenarios: 1. [Simple approval](#1-simple-approval): Alice approves Bob to transfer her token. -2. [Approval with cross-contract call (XCC)](#2-approval-with-cross-contract-call): Alice approves Market to transfer one of her tokens and passes `msg` so that NFT will call `mt_on_approve` on Market's contract. +2. [Approval with cross-contract call (XCC)](#2-approval-with-cross-contract-call): Alice approves Market to transfer one of her tokens and passes `msg` so that MT will call `mt_on_approve` on Market's contract. 3. [Approval with XCC, edge case](#3-approval-with-cross-contract-call-edge-case): Alice approves Bazaar and passes `msg` again, but what's this? Bazaar doesn't implement `mt_on_approve`, so Alice sees an error in the transaction result. Not to worry, though, she checks `mt_is_approved` and sees that she did successfully approve Bazaar, despite the error. 4. [Approval IDs](#4-approval-ids): Bob buys Alice's token via Market. 5. [Approval IDs, edge case](#5-approval-ids-edge-case): Bob transfers same token back to Alice, Alice re-approves Market & Bazaar. Bazaar has an outdated cache. Bob tries to buy from Bazaar at the old price. @@ -109,7 +109,7 @@ Alice approves Market to transfer some of her tokens and passes `msg` so that MT 1. Using near-cli: near call mt mt_approve '{ - "token_id": ["1","2"], + "token_ids": ["1","2"], "amounts": ["1", "100"], "account_id": "market", "msg": "{\"action\": \"list\", \"price\": [\"100\",\"50\"],\"token\": \"nDAI\" }" @@ -120,7 +120,7 @@ Alice approves Market to transfer some of her tokens and passes `msg` so that MT 2. `mt` schedules a call to `mt_on_approve` on `market`. Using near-cli notation for easy cross-reference with the above, this would look like: near call market mt_on_approve '{ - "token_id": ["1","2"], + "token_ids": ["1","2"], "amounts": ["1","100"], "owner_id": "alice", "approval_ids": ["4","5"], @@ -176,7 +176,7 @@ Not to worry, though, she checks `mt_is_approved` and sees that she did successf ### 4. Approval IDs -Bob buys Alice's token via Market. Bob probably does this via Market's frontend, which will probably initiate the transfer via a call to `ft_transfer_call` on the nDAI contract to transfer 100 nDAI to `market`. Like the NFT standard's "transfer and call" function, [Fungible Token](../FungibleToken/Core.md)'s `ft_transfer_call` takes a `msg` which `market` can use to pass along information it will need to pay Alice and actually transfer the MT. The actual transfer of the MT is the only part we care about here. +Bob buys Alice's token via Market. Bob probably does this via Market's frontend, which will probably initiate the transfer via a call to `ft_transfer_call` on the nDAI contract to transfer 100 nDAI to `market`. Like the MT standard's "transfer and call" function, [Fungible Token](../FungibleToken/Core.md)'s `ft_transfer_call` takes a `msg` which `market` can use to pass along information it will need to pay Alice and actually transfer the MT. The actual transfer of the MT is the only part we care about here. **High-level explanation** @@ -189,6 +189,7 @@ Using near-cli notation for consistency: near call mt mt_transfer '{ "receiver_id": "bob", "token_id": "1", + "amount": "1", "approval_id": 2, }' --accountId market --amount .000000000000000000000001 @@ -206,6 +207,7 @@ Using near-cli notation for consistency: near call mt mt_transfer '{ "receiver_id": "bob", "token_id": "1", + "amount": "1", "approval_id": 3, }' --accountId bazaar --amount .000000000000000000000001 @@ -241,7 +243,7 @@ Again, note that no previous approvers will get cross-contract calls in this cas ## Reference-level explanation -The `Token` structure returned by `mt_token` must include an `approvals` field, which is a map of account IDs to `Approval`. The `amount` field though wrapped in quotes and treated like strings, the number will be stored as an unsigned integer with 128 bits. +The `Token` structure returned by `mt_tokens` must include an `approvals` field, which is a map of account IDs to `Approval`. The `amount` field though wrapped in quotes and treated like strings, the number will be stored as an unsigned integer with 128 bits. in approval is Using TypeScript's [Record type](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype) notation: ```diff @@ -346,7 +348,7 @@ The MT contract must implement the following methods: // * 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 `nft_revoke_all` to exceed +// * 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 @@ -376,7 +378,7 @@ function mt_approve( // Requirements // * Caller of the method must attach a deposit of 1 yoctoⓃ for security // purposes -// * If contract requires >1yN deposit on `nft_approve`, contract +// * 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 // @@ -393,7 +395,7 @@ function mt_revoke( // Requirements // * Caller of the method must attach a deposit of 1 yoctoⓃ for security // purposes -// * If contract requires >1yN deposit on `nft_approve`, contract +// * If contract requires >1yN deposit on `mt_approve`, contract // MUST refund all associated storage deposit when owner revokes approvals // * Contract MUST panic if called by someone other than token owner // @@ -423,7 +425,7 @@ function mt_revoke_all(token_ids: [string]) {} // stored as an array of unsigned integer with 128 bits. // // Returns: -// if `approval_id` given, `true` if `approved_account_id` is approved with given `approval_id` +// 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 @@ -470,7 +472,7 @@ If a contract that gets approved to transfer MTs wants to, it can implement `mt_ // 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. -// * `approval_id`: the approval ID stored by NFT contract for this approval. +// * `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 @@ -490,4 +492,4 @@ Further note that there is no parallel `mt_on_revoke` when revoking either a sin ### No incurred cost for core MT behavior -MT contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of `approvals` for calls to `mt_*` methods other than `mt_token`. See `near-contract-standards` [implementation of `ft_metadata` using `LazyOption`](https://github.com/near/near-sdk-rs/blob/c2771af7fdfe01a4e8414046752ee16fb0d29d39/examples/fungible-token/ft/src/lib.rs#L71) as a reference example. \ No newline at end of file +MT contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of `approvals` for calls to `mt_*` methods other than `mt_tokens`. See `near-contract-standards` [implementation of `ft_metadata` using `LazyOption`](https://github.com/near/near-sdk-rs/blob/c2771af7fdfe01a4e8414046752ee16fb0d29d39/examples/fungible-token/ft/src/lib.rs#L71) as a reference example. \ No newline at end of file From b29ddca05393e144e426d8dbb6a82421ad47c7e3 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 30 Sep 2021 20:40:59 -0700 Subject: [PATCH 072/108] chore: refactor naming consistency , typo, lint with events Co-authored-by: Mike Purvis --- specs/Standards/MultiToken/Events.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index 260267a3b..d307b05bb 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -43,7 +43,7 @@ type MtEvent = "mt_mint" | "mt_burn" | "mt_transfer" | "mt_approval" // * `EVENT_JSON`: The standard event prefix required to signal consumers about // the type of log data being emitted. // * `standard`: name of standard e.g. nep-246 -// * `version`: e.g. 1.0.0 +// * `version`: e.g. "1.0.0" // * `event`: `mt_mint` | `mt_burn` | `mt_transfer` | `mt_approval` // * `data`: associate event data interface MtEventLogData { @@ -64,7 +64,7 @@ interface MtEventLogData { // * `token_ids`: the tokens minted // * `amounts`: the number of tokens minted, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer -// . array with 128 bits. +// array with 128 bits. interface MtMintLog { owner_id: string, token_ids: string[], @@ -77,10 +77,10 @@ interface MtMintLog { // Fields // * Contract token_ids and amounts MUST be the same length // * `owner_id`: the account whose token(s) are being burned -// * `token_ids`: the tokens to burned +// * `token_ids`: the tokens being burned // * `amounts`: the number of tokens burned, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer -// . array with 128 bits. +// array with 128 bits. interface MtBurnLog { owner_id: string, token_ids: string[] @@ -91,12 +91,12 @@ interface MtBurnLog { // Requirements // * Contract MUST emit event when transferring a token // Fields -// * `sender_id`: the account sending the minted tokens -// * `receiver_id`: the account receving the minted tokens +// * `sender_id`: the account sending the tokens +// * `receiver_id`: the account receiving the tokens // * `token_ids`: the tokens to transfer -// * `amounts`: the number of tokens burned, wrapped in quotes and treated +// * `amounts`: the number of tokens to transfer, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer -// . array with 128 bits. +// array with 128 bits. interface MtTransferLog { sender_id: string, receiver_id: string, @@ -127,15 +127,15 @@ interface MtTransferLog { // * Contract MUST emit event when approval of tokens have changed // * Contract token_ids and amounts MUST be the same length // Fields -// * `account_id`: the account sending the approval -// * `approved_id`: the account being approved +// * `owner_id`: the account who owns the token, sending the approval +// * `approved_account_id`: the account being approved // * `token_ids`: the tokens to transfer // * `amounts`: the number of tokens approved for transfer, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer -// . array with 128 bits. +// array with 128 bits. interface MtApprovalLog { account_id: string, - approved_id: string, + approved_account_id: string, token_ids: string[], amounts: string[] } From 791e5ec9bb16a83988495a3afc8717697f6d68af Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 30 Sep 2021 20:42:37 -0700 Subject: [PATCH 073/108] chore: refactor to match event description Co-authored-by: Mike Purvis --- specs/Standards/MultiToken/Events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index d307b05bb..941b498e9 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -134,7 +134,7 @@ interface MtTransferLog { // like a string, although the numbers will be stored as an unsigned integer // array with 128 bits. interface MtApprovalLog { - account_id: string, + owner_id: string, approved_account_id: string, token_ids: string[], amounts: string[] From 44ee2a9f1090ee68e44728fe632e133fad706d09 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 30 Sep 2021 20:43:56 -0700 Subject: [PATCH 074/108] chore: typos and lint with metadata Co-authored-by: Mike Purvis --- specs/Standards/MultiToken/Metadata.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index 789822033..2317b6143 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -9,7 +9,7 @@ An interface for a multi token's metadata. The goal is to keep the metadata futu ## Motivation -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 NFT's metadata. +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/docs/concepts/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. @@ -24,7 +24,7 @@ Prior art: ## Interface -Metadata applies at both the class level (`BaseTokenMetadata`) and the specific instance level (`TokenMetadata`). The relevant metadata for each: +Metadata applies at both the class level (`MTBaseTokenMetadata`) and the specific instance level (`MTTokenMetadata`). The relevant metadata for each: ```ts @@ -69,10 +69,10 @@ 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: TokenId[]): MTAllTokenMetadata[] -function mt_metadata_token_by_token_id(token_ids: TokenId[]): MTTokenMetadata[] -function mt_metadata_base_by_token_id(token_ids: TokenId[]): MTBaseMetadata[] -function mt_metadata_base_by_metadata_id(base_metadata_ids: MTBaseMetadataId[]):MTBaseTokenMetadata +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[]): MTBaseMetadata[] +function mt_metadata_base_by_metadata_id(base_metadata_ids: string[]): MTBaseTokenMetadata ``` @@ -112,19 +112,19 @@ For `MTTokenMetadata`: - `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`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token expires -- `starts_at`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token starts being valid -- `updated_at`: [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) datetime when token was last updated +- `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 `MTAllTokenMetadata`: +For `MTTokenMetadataAll `: -- `base`: The base metadata that corresonds to `MTBaseTokenMetadata` for the token. +- `base`: The base metadata that corresponds to `MTBaseTokenMetadata` for the token. - `token`: The token specific metadata that corresponds to `MTTokenMetadata`. -### No incurred cost for core NFT behavior +### No incurred cost for core MT behavior Contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of metadata for calls to `mt_*` methods other than `mt_metadata*` or `mt_tokens`. See `near-contract-standards` [implementation using `LazyOption`](https://github.com/near/near-sdk-rs/blob/c2771af7fdfe01a4e8414046752ee16fb0d29d39/examples/fungible-token/ft/src/lib.rs#L71) as a reference example. From 35a610ada1ac16e1e8df8c674b37326f5d661956 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 1 Oct 2021 09:19:51 -0700 Subject: [PATCH 075/108] chore: rm dead code for mt_approvals --- .../MultiToken/ApprovalManagement.md | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index aa163d112..232c31d01 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -52,7 +52,6 @@ Alice approves Bob to transfer her tokens. 1. Alice approves Bob 2. Alice queries the token to verify -3. Alice verifies a different way **Technical calls** @@ -65,33 +64,13 @@ Alice approves Bob to transfer her tokens. The response: '' - -2. Alice calls view method `mt_approvals`: - - near view mt mt_approvals \ - '{ "token_id": ["1", "2"] }' - - The response: - - { - "ids": ["1", "2"] - "owner_id": "alice.near", - "approvals": [{ - "bob": 4, - "amount": 1, - }, - { - "bob": 5, - "amount": 100, - }] - } - -3. Alice calls view method `mt_is_approved`: +2. Alice calls view method `mt_is_approved`: near view mt mt_is_approved \ '{ "token_ids": ["1", "2"], amounts:["1","100"], "approved_account_id": "bob" }' The response: + true ### 3. Approval with cross-contract call @@ -435,6 +414,8 @@ function mt_is_approved( amounts: [string], approval_ids: number[]|null ): boolean {} + + ``` ### Why must `mt_approve` panic if `mt_revoke_all` would fail later? From 49a356edcac46b8a6a7450ce6701163702ed7553 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 1 Oct 2021 09:55:18 -0700 Subject: [PATCH 076/108] chore: comment argument order matches impl order --- specs/Standards/MultiToken/ApprovalManagement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index 232c31d01..f067cb7cd 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -448,11 +448,11 @@ 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 -// * `owner_id`: the owner of the token // * `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 From 23b8c0af0dc0c4f280fb63745c3ef293976b6c42 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 1 Oct 2021 10:01:33 -0700 Subject: [PATCH 077/108] chore: reorder comments to match impl args --- specs/Standards/MultiToken/ApprovalManagement.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index f067cb7cd..4a5fca938 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -396,12 +396,12 @@ 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 `approvals` -// * `approval_ids`: an optional array of approval IDs to check against +// * `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`. -// * `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. // // Returns: // if `approval_ids` is given, `true` if `approved_account_id` is approved with given `approval_id` From 46365af5eadc4e81b4c6470cddc35dd68bf310b9 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 1 Oct 2021 10:05:50 -0700 Subject: [PATCH 078/108] chore: rm duplicate event description --- specs/Standards/MultiToken/Events.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index 941b498e9..bcafaabe8 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -104,24 +104,6 @@ interface MtTransferLog { amounts: string[] } -// Transfer event log. Emitted when a token is transfered. -// Requirements -// * Contract MUST emit event when transferring a token -// * Contract token_ids and amounts MUST be the same length -// Fields -// * `sender_id`: the account sending the minted tokens -// * `receiver_id`: the account receving the minted tokens -// * `token_ids`: the tokens to transfer -// * `amounts`: the number of tokens burned, wrapped in quotes and treated -// like a string, although the numbers will be stored as an unsigned integer -// . array with 128 bits. -interface MtTransferLog { - sender_id: string, - receiver_id: string, - token_ids: string[], - amounts: string[] -} - // Approval event log. Emitted when a token's approval has changed. // Requirements // * Contract MUST emit event when approval of tokens have changed From 025d5e930df3474d709ed37b3ca1a6f6695c4dc0 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 1 Oct 2021 10:17:42 -0700 Subject: [PATCH 079/108] fix: adjust consistency between standards for mt_tokens --- specs/Standards/MultiToken/Core.md | 2 +- specs/Standards/MultiToken/Enumeration.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 0f19da249..2215d83c4 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -230,7 +230,7 @@ function mt_batch_transfer_call( // Returns the tokens with the given `token_ids` or `null` if no such token. -function mt_tokens(token_ids: string[]) (Token | null)[] +function mt_token(token_ids: string[]) (Token | null)[] // Returns the balance of an account for the given `token_id`. // The balance though wrapped in quotes and treated like a string, diff --git a/specs/Standards/MultiToken/Enumeration.md b/specs/Standards/MultiToken/Enumeration.md index d891aa1af..c59966ced 100644 --- a/specs/Standards/MultiToken/Enumeration.md +++ b/specs/Standards/MultiToken/Enumeration.md @@ -32,7 +32,7 @@ The contract must implement the following view methods: // representing the starting index of tokens to return // * `limit`: the maximum number of tokens to return // -// Returns an array of MTToken objects, as described above, and an empty array if there are no tokens +// Returns an array of Token objects, as described above, 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) From 807aa0e5e55e41ae86e3776a8459da59fc683524 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 1 Oct 2021 10:22:20 -0700 Subject: [PATCH 080/108] chore: fix comments referencing Token and MTBaseTokenMetadata --- specs/Standards/MultiToken/Enumeration.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/Enumeration.md b/specs/Standards/MultiToken/Enumeration.md index c59966ced..62c1e491b 100644 --- a/specs/Standards/MultiToken/Enumeration.md +++ b/specs/Standards/MultiToken/Enumeration.md @@ -32,7 +32,8 @@ The contract must implement the following view methods: // representing the starting index of tokens to return // * `limit`: the maximum number of tokens to return // -// Returns an array of Token objects, as described above, and an empty array if there are no tokens +// 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) @@ -64,7 +65,7 @@ The contract must implement the following view methods if using metadata extensi // representing the starting index of tokens to return // * `limit`: the maximum number of tokens to return // -// Returns an array of MTBaseTokenMetadata objects, as described above, and an empty array if there are no tokens +// 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 From 4d9a8a5040ac95df10f90d1bed9ac962916ae9e6 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 1 Oct 2021 10:37:55 -0700 Subject: [PATCH 081/108] chore: consistent spacing between metadata fields --- specs/Standards/MultiToken/Metadata.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index 2317b6143..4d5988e58 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -36,9 +36,9 @@ type MTContractMetadata = { 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" + 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 // 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. From 3849190a42dc2b1b344ee7179b6249d668ea5460 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 1 Oct 2021 11:52:38 -0700 Subject: [PATCH 082/108] chore: newline --- specs/Standards/MultiToken/Metadata.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index 4d5988e58..9c5d86171 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -58,6 +58,7 @@ type MTTokenMetadata = { 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 From 8dc2bc91d15e097cda37876f25a0e4fb624fba46 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Mon, 25 Oct 2021 21:19:20 -0700 Subject: [PATCH 083/108] fix: refactor events to match NFT events per standard spec --- specs/Standards/MultiToken/Events.md | 164 ++++++++++++++++++++++++--- 1 file changed, 149 insertions(+), 15 deletions(-) diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index bcafaabe8..09aafd4e0 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -5,13 +5,13 @@ Version `1.0.0` ## Summary -Standard interfaces for emitting events for a Multi Token Contract. +Standard interfaces for Multi Token Contract actions. ## Motivation -MT driven apps such as marketplaces and videogames perform a few -core actions minting, burning, transferring, and approving tokens -for transfer. +MT-driven apps such as marketplaces and videogames perform a few +core actions `minting`, `burning`, `transferring`, and +`approving tokens for transfer`. Each app has their own way of performing these actions and it is difficult to consistently capture these actions. We codify these @@ -28,9 +28,67 @@ This enables indexers, and systems to be able to build a consistent view of the Prior Art: - [ERC-721]'s events - [ERC-1155]'s events +- [NEP-254]'s events + +## Events + +Many apps use different interfaces that represent the same action. +This interface standardizes that process by introducing event logs. +There is no Event NEP yet, so this standard paves the road to that. + +Events use standard logs capability of NEAR and defined as a convention. +Events are log entries that start with `EVENT_JSON:` prefix followed by a single valid JSON document of the following interface: + +```ts +// Interface to capture data +// about an event +// Arguments +// * `standard`: name of standard e.g. nep171 +// * `version`: e.g. 1.0.0 +// * `event`: string +// * `data`: associate event data +interface EventLogData { + standard: string, + version: string, + event: string, + data?: unknown, +} +``` + +#### Valid event logs: + +```js +EVENT_JSON:{"standard": "nepXXX", "version": "1.0.0", "event": "xyz_is_triggered"} +``` + +```js +EVENT_JSON:{ + "standard": "nepXXX", + "version": "1.0.0", + "event": "xyz_is_triggered" +} +``` + +```js +EVENT_JSON:{"standard": "nepXXX", "version": "1.0.0", "event": "xyz_is_triggered", "data": {"triggered_by": "foundation.near"}} +``` + +#### Invalid event logs: + +* Two events in a single log entry (instead, call `log` for each individual event) +``` +EVENT_JSON:{"standard": "nepXXX", "version": "1.0.0", "event": "xyz_is_triggered"} +EVENT_JSON:{"standard": "nepXXX", "version": "1.0.0", "event": "xyz_is_triggered"} +``` +* Invalid JSON data +``` +EVENT_JSON:invalid json +``` ## Interface +Multi Token Events MUST have `standard` set to `"nep246"`, standard version set to `"1.0.0"`, `event` value is one of `mt_mint`, `mt_burn`, `mt_transfer`, `mt_approval` and `data` must be of one of the following relavant types: `MtMintLog[] | MtBurnLog[] | MtTransferLog[] | MtApprovalLog[]`: + ```ts @@ -48,8 +106,8 @@ type MtEvent = "mt_mint" | "mt_burn" | "mt_transfer" | "mt_approval" // * `data`: associate event data interface MtEventLogData { EVENT_JSON: { - standard: string, - version: string, + standard: "nep246", + version: "1.0.0", event: MtEvent, data: MtMintLog[] | MtBurnLog[] | MtTransferLog[] | MtApprovalLog[] } @@ -65,10 +123,12 @@ interface MtEventLogData { // * `amounts`: the number of tokens minted, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer // array with 128 bits. +// * `memo`: optional message interface MtMintLog { owner_id: string, token_ids: string[], amounts: string[], + memo?: string } // Burning event log. Emitted when a token is burned. @@ -77,33 +137,39 @@ interface MtMintLog { // Fields // * Contract token_ids and amounts MUST be the same length // * `owner_id`: the account whose token(s) are being burned +// * `authorized_id`: approved account to burn, if applicable // * `token_ids`: the tokens being burned // * `amounts`: the number of tokens burned, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer // array with 128 bits. +// * `memo`: optional message interface MtBurnLog { owner_id: string, - token_ids: string[] - amounts: string[] + authorized_id?: string, + token_ids: string[], + amounts: string[], + memo?: string } // Transfer event log. Emitted when a token is transfered. // Requirements // * Contract MUST emit event when transferring a token // Fields -// * `sender_id`: the account sending the tokens -// * `receiver_id`: the account receiving the tokens +// * `authorized_id`: approved account to transfer +// * `old_owner_id`: the account sending the tokens "sender.near" +// * `new_owner_id`: the account receiving the tokens "receiver.near" // * `token_ids`: the tokens to transfer // * `amounts`: the number of tokens to transfer, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer // array with 128 bits. interface MtTransferLog { - sender_id: string, - receiver_id: string, + authorized_id?: string, + old_owner_id: string, + new_owner_id: string, token_ids: string[], - amounts: string[] + amounts: string[], + memo?: string } - // Approval event log. Emitted when a token's approval has changed. // Requirements // * Contract MUST emit event when approval of tokens have changed @@ -115,15 +181,82 @@ interface MtTransferLog { // * `amounts`: the number of tokens approved for transfer, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer // array with 128 bits. +// * `memo`: optional message interface MtApprovalLog { owner_id: string, approved_account_id: string, token_ids: string[], - amounts: string[] + amounts: string[], + memo?: string +} + +``` + +## Examples + +Single owner minting (pretty-formatted for readability purposes): + +```js +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_mint", + "data": [ + {"owner_id": "foundation.near", "token_ids": ["aurora", "proximitylabs_ft"], "amounts":["1", "100"]} + ] +} +``` + +Different owners minting: + +```js +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_mint", + "data": [ + {"owner_id": "foundation.near", "token_ids": ["aurora", "proximitylabs_ft"], "amounts":["1","100"]}, + {"owner_id": "user1.near", "token_ids": ["meme"], "amounts": ["1"]} + ] } +``` +Different events (separate log entries): + +```js +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_burn", + "data": [ + {"owner_id": "foundation.near", "token_ids": ["aurora", "proximitylabs_ft"], "amounts": ["1","100"]}, + ] +} ``` +```js +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_transfer", + "data": [ + {"old_owner_id": "user1.near", "new_owner_id": "user2.near", "token_ids": ["meme"], "amounts":["1"], "memo": "have fun!"} + ] +} +``` + +```js +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_approval", + "data": [ + {"owner_id": "user1.near", "approved_account_id": "market.near", "token_ids": ["meme"], "amounts":["1"], "memo": "have fun at the market!"} + ] +} +``` + + ## Drawbacks There is a known limitation of 16kb strings when capturing logs. @@ -134,4 +267,5 @@ be executed may vary. [ERC-721]: https://eips.ethereum.org/EIPS/eip-721 [ERC-1155]: https://eips.ethereum.org/EIPS/eip-1155 [storage]: https://docs.near.org/docs/concepts/storage-staking + [NEP-254]: https://github.com/near/NEPs/issues/254 From dc57e041079a4835aeaa85373dfe321d9608aab5 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 2 Nov 2021 10:34:03 -0700 Subject: [PATCH 084/108] chore: fix link consistency within markdown --- specs/Standards/MultiToken/Core.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 2215d83c4..2d01324ad 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -21,9 +21,9 @@ which allows, a user to attach many tokens to a call to a separate contract. Add Prior art: - [ERC-721] -- [EIP-1155 for multi-tokens](https://eips.ethereum.org/EIPS/eip-1155) -- [NEAR's Fungible Token Standard](../FungibleToken/Core.md), which first pioneered the "transfer and call" technique -- [NEAR's Non-Fungible Token Standard](../NearFungibleToken/Core.md) +- [ERC-1155] +- [NEAR Fungible Token Standard][FT], which first pioneered the "transfer and call" technique +- [NEAR Non-Fungible Token Standard][NFT] ## Reference-level explanation @@ -352,4 +352,5 @@ function mt_on_transfer( [Metadata]: Metadata.md [NFT]: ../NonFungibleToken/Core.md [Approval Management]: ApprovalManagement.md + [FT]: ../FungibleToken/Core.md From 1adf7b6dc873b5be127d8109bf730b051f76fe4a Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 2 Nov 2021 10:36:36 -0700 Subject: [PATCH 085/108] fix: token_ids to be an array for batch_balance_of --- specs/Standards/MultiToken/Core.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 2d01324ad..1e43247f7 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -246,7 +246,7 @@ function mt_balance_of(account_id: string, token_id: string): string // Arguments: // * `account_id`: the NEAR account that owns the tokens. // * `token_ids`: the tokens to retrieve the balance from -function mt_batch_balance_of(account_id: string, token_ids: string): string[] +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 From 37940cd04cc756427b80f30fd36503612fd08b57 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 2 Nov 2021 10:37:52 -0700 Subject: [PATCH 086/108] fix: confusing comment on the return for mt_resolve_transfer --- specs/Standards/MultiToken/Core.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 1e43247f7..20da12f96 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -289,7 +289,7 @@ The following behavior is required, but contract authors may name this function // set of original approved accounts in this argument, and restore these // approved accounts in case of revert. // -// Returns total amount spent by the `sender_id`, corresponding to the `token_id`. +// Returns total amount spent by the `receiver_id`, corresponding to the `token_id`. // The amounts returned, though wrapped in quotes and treated like strings, // the numbers will be stored as an unsigned integer with 128 bits. // Example: if sender_id calls `mt_transfer_call({ "amounts": ["100"], token_ids: ["55"], receiver_id: "games" })`, From 5422f189a5ca8ff5f00dab6c98585eeee5030c45 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 2 Nov 2021 10:40:04 -0700 Subject: [PATCH 087/108] chore: remove misleading comment from transfer methods --- specs/Standards/MultiToken/Core.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 20da12f96..74a6271e1 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -120,7 +120,6 @@ function mt_batch_transfer( memo: string|null, ) {} -// Returns `true` if the token was transferred from the sender's account. // Transfer token and call a method on a receiver contract. A successful // workflow will end in a success execution outcome to the callback on the MT @@ -171,7 +170,6 @@ function mt_transfer_call( ): Promise {} -// Returns `true` if the tokens were transferred from the sender's account. // Transfer tokens and call a method on a receiver contract. A successful // workflow will end in a success execution outcome to the callback on the MT From e2a23854ce4506d56a7609fe4fd23484e8d46bc8 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 2 Nov 2021 10:44:59 -0700 Subject: [PATCH 088/108] fix: base_by_metadata_id should return array --- specs/Standards/MultiToken/Metadata.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index 9c5d86171..48d42fe37 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -72,8 +72,8 @@ A new set of functions MUST be supported on the MT contract: 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[]): MTBaseMetadata[] -function mt_metadata_base_by_metadata_id(base_metadata_ids: string[]): MTBaseTokenMetadata +function mt_metadata_base_by_token_id(token_ids: string[]): MTBaseTokenMetadata[] +function mt_metadata_base_by_metadata_id(base_metadata_ids: string[]): MTBaseTokenMetadata[] ``` From f9c5c9d8f8440ac19a466af49dd198f5557c59a3 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 3 Dec 2021 15:12:15 -0800 Subject: [PATCH 089/108] feat: add rationale section to further clarify decisions --- specs/Standards/MultiToken/Core.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 74a6271e1..787b8fed5 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -24,6 +24,21 @@ Prior art: - [ERC-1155] - [NEAR Fungible Token Standard][FT], which first pioneered the "transfer and call" technique - [NEAR Non-Fungible Token Standard][NFT] +## Rationale + +Why have another standard, aren't fungible and non-fungible tokens enough? The current fungible token and non-fungible token standards, do not provide support for performing batch transactions, representing many tokens in a single contract, as well as the flexibility to define different token types with different behavior in a single contract. This is something that makes it impossible to be interoperable with other major blockchain networks such as Ethereum. + +The standard here introduces a few concepts that evolve the original [ERC-1155] standard to have more utility, while maintaining the original flexibilty of the standard. So keeping that in mind, there is no explicit token type. Where the token type is set to be something that is FT or NFT or something in between. + +The decision to not have an explicit token type was taken to allow the community to define their own standards and meanings through metadata. As standards evolve on other networks, this specification allows the standard to be able to represent tokens across networks accurately, without necessarily restricting the behavior to any preset definition. + +The issues with this in general is a problem with defining what metadata means and how is that interpeted. We have chosen to follow the pattern that is currently in use on Ethereum in the [ERC-1155] standard. That pattern relies on people to make extensions or to make signals as to how they want the metadata to be represented for their use case. + +One of the areas that has broad sweeping implications from the [ERC-1155] standard is the lack of direct access to metadata. With Near's sharding we are able to have a [Metadata Extension](Metadata.md) for the standard that exists on chain. So developers and users are not required to use an indexer to understand, how to interact or interpet tokens, via token identifiers that they receive. + +Another extension that we made was to provide an explict ability for developers and users to group or link together series of NFTs/FTs or any combination of tokens. This provides additional flexiblity that the [ERC-1155] standard only has loose guidelines on. This was chosen to make it easy for consumers to understand the relationship between tokens within the contract. + +To recap, we choose to create this standard, to improve interoperability, developer ease of use, and to extend token representability beyond what was available directly in the FT or NFT standards. We believe this to be another tool in the developer's toolkit. It makes it possible to represent many types of tokens and to enable exchanges of many tokens within a single `transaction`. ## Reference-level explanation From 6f32cd30320d35daf215be2f540f841f8cde0d3e Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 15 Dec 2021 16:27:17 -0800 Subject: [PATCH 090/108] chore: tidy account to account_id Co-authored-by: Olga Telezhnaya --- specs/Standards/MultiToken/Events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index 09aafd4e0..9d398111d 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -137,7 +137,7 @@ interface MtMintLog { // Fields // * Contract token_ids and amounts MUST be the same length // * `owner_id`: the account whose token(s) are being burned -// * `authorized_id`: approved account to burn, if applicable +// * `authorized_id`: approved account_id to burn, if applicable // * `token_ids`: the tokens being burned // * `amounts`: the number of tokens burned, wrapped in quotes and treated // like a string, although the numbers will be stored as an unsigned integer From f1a91b9bb3956b86a27186d1c0047661fc81e3e1 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 15 Dec 2021 15:49:22 -0800 Subject: [PATCH 091/108] chore: reword rationale to be more clear and less broad and more specific about limitations --- specs/Standards/MultiToken/Core.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 787b8fed5..c55cb859b 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -26,7 +26,7 @@ Prior art: - [NEAR Non-Fungible Token Standard][NFT] ## Rationale -Why have another standard, aren't fungible and non-fungible tokens enough? The current fungible token and non-fungible token standards, do not provide support for performing batch transactions, representing many tokens in a single contract, as well as the flexibility to define different token types with different behavior in a single contract. This is something that makes it impossible to be interoperable with other major blockchain networks such as Ethereum. +Why have another standard, aren't fungible and non-fungible tokens enough? The current fungible token and non-fungible token standards, do not provide support for representing many FT tokens in a single contract, as well as the flexibility to define different token types with different behavior in a single contract. This is something that makes it difficult to be interoperable with other major blockchain networks, that implement standards that allow for representation of many different FT tokens in a single contract such as Ethereum. The standard here introduces a few concepts that evolve the original [ERC-1155] standard to have more utility, while maintaining the original flexibilty of the standard. So keeping that in mind, there is no explicit token type. Where the token type is set to be something that is FT or NFT or something in between. From d3c77b4816338a2a6dceb575f69bfc48d90d2894 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 15 Dec 2021 16:10:49 -0800 Subject: [PATCH 092/108] chore: bring clarity to token type explanation --- specs/Standards/MultiToken/Core.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index c55cb859b..f2817c16d 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -28,9 +28,9 @@ Prior art: Why have another standard, aren't fungible and non-fungible tokens enough? The current fungible token and non-fungible token standards, do not provide support for representing many FT tokens in a single contract, as well as the flexibility to define different token types with different behavior in a single contract. This is something that makes it difficult to be interoperable with other major blockchain networks, that implement standards that allow for representation of many different FT tokens in a single contract such as Ethereum. -The standard here introduces a few concepts that evolve the original [ERC-1155] standard to have more utility, while maintaining the original flexibilty of the standard. So keeping that in mind, there is no explicit token type. Where the token type is set to be something that is FT or NFT or something in between. +The standard here introduces a few concepts that evolve the original [ERC-1155] standard to have more utility, while maintaining the original flexibilty of the standard. So keeping that in mind, we are defining this as a new token type. It combines two main features of FT and NFT. It allows us to represent many token types in a single contract, and it's possible to store the amount for each token. -The decision to not have an explicit token type was taken to allow the community to define their own standards and meanings through metadata. As standards evolve on other networks, this specification allows the standard to be able to represent tokens across networks accurately, without necessarily restricting the behavior to any preset definition. +The decision to not use FT and NFT as explict token types was taken to allow the community to define their own standards and meanings through metadata. As standards evolve on other networks, this specification allows the standard to be able to represent tokens across networks accurately, without necessarily restricting the behavior to any preset definition. The issues with this in general is a problem with defining what metadata means and how is that interpeted. We have chosen to follow the pattern that is currently in use on Ethereum in the [ERC-1155] standard. That pattern relies on people to make extensions or to make signals as to how they want the metadata to be represented for their use case. From c8af57e9f5526662957cb264efcf50af274b53ea Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 15 Dec 2021 16:39:27 -0800 Subject: [PATCH 093/108] fix: add additional examples of authorized_id and clarify account_id --- specs/Standards/MultiToken/Events.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index 9d398111d..5ec1b73c7 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -155,7 +155,7 @@ interface MtBurnLog { // Requirements // * Contract MUST emit event when transferring a token // Fields -// * `authorized_id`: approved account to transfer +// * `authorized_id`: approved account_id to transfer // * `old_owner_id`: the account sending the tokens "sender.near" // * `new_owner_id`: the account receiving the tokens "receiver.near" // * `token_ids`: the tokens to transfer @@ -232,6 +232,15 @@ EVENT_JSON:{ {"owner_id": "foundation.near", "token_ids": ["aurora", "proximitylabs_ft"], "amounts": ["1","100"]}, ] } + +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_burn", + "data": [ + {"owner_id": "foundation.near", "token_ids": ["aurora_alpha", "proximitylabs_ft"], "amounts": ["1","100"], "authorized_id": "thirdparty.near" }, + ] +} ``` ```js @@ -243,6 +252,15 @@ EVENT_JSON:{ {"old_owner_id": "user1.near", "new_owner_id": "user2.near", "token_ids": ["meme"], "amounts":["1"], "memo": "have fun!"} ] } + +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_transfer", + "data": [ + {"old_owner_id": "user2.near", "new_owner_id": "user3.near", "token_ids": ["meme"], "amounts":["1"], "authorized_id": "thirdparty.near", "memo": "have fun!"} + ] +} ``` ```js From 6c93dd18d4ed7912881db59f83508f509ad70301 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 4 Feb 2022 15:10:48 -0800 Subject: [PATCH 094/108] fix: align mt standard wth nft standard ref/ event standard --- specs/Standards/MultiToken/Events.md | 145 ++++----------------------- 1 file changed, 20 insertions(+), 125 deletions(-) diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index 5ec1b73c7..48c113351 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -6,113 +6,32 @@ Version `1.0.0` ## Summary Standard interfaces for Multi Token Contract actions. +Extension of [NEP-297](../EventsFormat.md) ## Motivation -MT-driven apps such as marketplaces and videogames perform a few -core actions `minting`, `burning`, `transferring`, and -`approving tokens for transfer`. - -Each app has their own way of performing these actions and it -is difficult to consistently capture these actions. We codify these -actions as events, in order to capture them from a variety of context -within the MT Contract. This specification enables the events to be -broadly consumed by indexers, developers, and interested systems. - - -Contract implementers are required to emit events for the actions they take. -These actions are `minting`,`burning`,`transferring`, and `approving tokens to be transferred`. - -This enables indexers, and systems to be able to build a consistent view of the contract and take action on the contract without polling. - -Prior Art: -- [ERC-721]'s events -- [ERC-1155]'s events -- [NEP-254]'s events - -## Events - -Many apps use different interfaces that represent the same action. -This interface standardizes that process by introducing event logs. -There is no Event NEP yet, so this standard paves the road to that. - -Events use standard logs capability of NEAR and defined as a convention. -Events are log entries that start with `EVENT_JSON:` prefix followed by a single valid JSON document of the following interface: - -```ts -// Interface to capture data -// about an event -// Arguments -// * `standard`: name of standard e.g. nep171 -// * `version`: e.g. 1.0.0 -// * `event`: string -// * `data`: associate event data -interface EventLogData { - standard: string, - version: string, - event: string, - data?: unknown, -} -``` - -#### Valid event logs: - -```js -EVENT_JSON:{"standard": "nepXXX", "version": "1.0.0", "event": "xyz_is_triggered"} -``` - -```js -EVENT_JSON:{ - "standard": "nepXXX", - "version": "1.0.0", - "event": "xyz_is_triggered" -} -``` - -```js -EVENT_JSON:{"standard": "nepXXX", "version": "1.0.0", "event": "xyz_is_triggered", "data": {"triggered_by": "foundation.near"}} -``` - -#### Invalid event logs: - -* Two events in a single log entry (instead, call `log` for each individual event) -``` -EVENT_JSON:{"standard": "nepXXX", "version": "1.0.0", "event": "xyz_is_triggered"} -EVENT_JSON:{"standard": "nepXXX", "version": "1.0.0", "event": "xyz_is_triggered"} -``` -* Invalid JSON data -``` -EVENT_JSON:invalid json -``` +NEAR and third-party applications need to track + `mint`, `burn`, `transfer` events for all MT-driven apps consistently. This exension addresses that. +Note that applications, including NEAR Wallet, could require implementing additional methods to display tokens correctly such as [`mt_metadata`](Metadata.md) and [`mt_tokens_for_owner`](Enumeration.md). ## Interface -Multi Token Events MUST have `standard` set to `"nep246"`, standard version set to `"1.0.0"`, `event` value is one of `mt_mint`, `mt_burn`, `mt_transfer`, `mt_approval` and `data` must be of one of the following relavant types: `MtMintLog[] | MtBurnLog[] | MtTransferLog[] | MtApprovalLog[]`: +Multi Token Events MUST have `standard` set to `"nep246"`, 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 relavant types: `MtMintLog[] | MtBurnLog[] | MtTransferLog[]`: ```ts -// Interface to capture an event. The indexer looks for events prefixed -// with EVENT_JSON. The rest follows the EventLog -type MtEvent = "mt_mint" | "mt_burn" | "mt_transfer" | "mt_approval" - -// Interface for MT contract event data. It is used to emit event data with -// the near standard logging. -// * `EVENT_JSON`: The standard event prefix required to signal consumers about -// the type of log data being emitted. -// * `standard`: name of standard e.g. nep-246 -// * `version`: e.g. "1.0.0" -// * `event`: `mt_mint` | `mt_burn` | `mt_transfer` | `mt_approval` -// * `data`: associate event data interface MtEventLogData { EVENT_JSON: { standard: "nep246", version: "1.0.0", event: MtEvent, - data: MtMintLog[] | MtBurnLog[] | MtTransferLog[] | MtApprovalLog[] + data: MtMintLog[] | MtBurnLog[] | MtTransferLog[] } } +``` +```ts // Minting event log. Emitted when a token is minted/created. // Requirements // * Contract MUST emit event when minting a token @@ -170,26 +89,6 @@ interface MtTransferLog { amounts: string[], memo?: string } -// Approval event log. Emitted when a token's approval has changed. -// Requirements -// * Contract MUST emit event when approval of tokens have changed -// * Contract token_ids and amounts MUST be the same length -// Fields -// * `owner_id`: the account who owns the token, sending the approval -// * `approved_account_id`: the account being approved -// * `token_ids`: the tokens to transfer -// * `amounts`: the number of tokens approved for transfer, wrapped in quotes and treated -// like a string, although the numbers will be stored as an unsigned integer -// array with 128 bits. -// * `memo`: optional message -interface MtApprovalLog { - owner_id: string, - approved_account_id: string, - token_ids: string[], - amounts: string[], - memo?: string -} - ``` ## Examples @@ -232,7 +131,11 @@ EVENT_JSON:{ {"owner_id": "foundation.near", "token_ids": ["aurora", "proximitylabs_ft"], "amounts": ["1","100"]}, ] } +``` + +Authorized id: +```js EVENT_JSON:{ "standard": "nep246", "version": "1.0.0", @@ -263,17 +166,15 @@ EVENT_JSON:{ } ``` -```js -EVENT_JSON:{ - "standard": "nep246", - "version": "1.0.0", - "event": "mt_approval", - "data": [ - {"owner_id": "user1.near", "approved_account_id": "market.near", "token_ids": ["meme"], "amounts":["1"], "memo": "have fun at the market!"} - ] -} -``` +## Further methods + +Note that the example events covered above cover two different kinds of events: +1. Events that are not specified in the MT Standard (`mt_mint`, `mt_burn`) +2. An event that is covered in the [Multi Token Core Standard](https://nomicon.io/Standards/MultiToken/Core.html#mt-interface). (`mt_transfer`) +This event standard also applies beyond the three events highlighted here, where future events follow the same convention of as the second type. For instance, if an MT contract uses the [approval management standard](https://nomicon.io/Standards/MultiToken/ApprovalManagement.html), it may emit an event for `mt_approve` if that's deemed as important by the developer community. + +Please feel free to open pull requests for extending the events standard detailed here as needs arise. ## Drawbacks @@ -281,9 +182,3 @@ There is a known limitation of 16kb strings when capturing logs. This can be observed from `token_ids` that may vary in length for different apps so the amount of logs that can be executed may vary. - - [ERC-721]: https://eips.ethereum.org/EIPS/eip-721 - [ERC-1155]: https://eips.ethereum.org/EIPS/eip-1155 - [storage]: https://docs.near.org/docs/concepts/storage-staking - [NEP-254]: https://github.com/near/NEPs/issues/254 - From cecb1a274d40eb5aa441425f5ab223b84ce982b6 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 22 Mar 2022 10:21:35 -0700 Subject: [PATCH 095/108] fix: update approvals to approved_account_ids to match other specs --- .../MultiToken/ApprovalManagement.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index 4a5fca938..9763d5043 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -222,7 +222,7 @@ Again, note that no previous approvers will get cross-contract calls in this cas ## Reference-level explanation -The `Token` structure returned by `mt_tokens` must include an `approvals` field, which is a map of account IDs to `Approval`. The `amount` field though wrapped in quotes and treated like strings, the number will be stored as an unsigned integer with 128 bits. +The `Token` structure returned by `mt_tokens` must include an `approved_account_ids` field, which is a map of account IDs to `Approval`. The `amount` field though wrapped in quotes and treated like strings, the number will be stored as an unsigned integer with 128 bits. in approval is Using TypeScript's [Record type](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype) notation: ```diff @@ -233,7 +233,7 @@ The `Token` structure returned by `mt_tokens` must include an `approvals` field, type Token = { id: string, owner_id: string, -+ approvals: Record, ++ approved_account_ids: Record, }; ``` @@ -242,7 +242,7 @@ Example token data: ```json { "id": "1", - "approvals": { + "approved_account_ids": { "bob.near": { "amount": "100", "approval_id":1, @@ -269,13 +269,13 @@ Note that while this describes an honest mistake, the possibility of such a bug To avoid this possibility, the MT contract generates a unique approval ID each time it approves an account. Then when calling `mt_transfer`, `mt_transfer_call`, `mt_batch_transfer`, or `mt_batch_transfer_call` the approved account passes `approval_id` or `approval_ids` with this value to make sure the underlying state of the token(s) hasn't changed from what the approved account expects. -Keeping with the example above, say the initial approval of the second marketplace generated the following `approvals` data: +Keeping with the example above, say the initial approval of the second marketplace generated the following `approved_account_ids` data: ```json { "id": "1", "owner_id": "alice.near", - "approvals": { + "approved_account_ids": { "marketplace_1.near": { "approval_id": 1, "amount": "100", @@ -287,13 +287,13 @@ Keeping with the example above, say the initial approval of the second marketpla } ``` -But after the transfers and re-approval described above, the token might have `approvals` as: +But after the transfers and re-approval described above, the token might have `approved_account_ids` as: ```json { "id": "1", "owner_id": "alice.near", - "approvals": { + "approved_account_ids": { "marketplace_2.near": { "approval_id": 3, "amount": "50", @@ -336,7 +336,7 @@ 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 `approvals` +// * `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. @@ -362,7 +362,7 @@ function mt_approve( // * Contract MUST panic if called by someone other than token owner // // Arguments: -// * `token_ids`: the token for which to revoke approvals +// * `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], @@ -375,11 +375,11 @@ function mt_revoke( // * 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 approvals +// 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 approvals to revoke +// * `token_ids`: the token ids with approved_account_ids to revoke function mt_revoke_all(token_ids: [string]) {} /****************/ @@ -395,7 +395,7 @@ 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 `approvals` +// * `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 @@ -473,4 +473,4 @@ Further note that there is no parallel `mt_on_revoke` when revoking either a sin ### No incurred cost for core MT behavior -MT contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of `approvals` for calls to `mt_*` methods other than `mt_tokens`. See `near-contract-standards` [implementation of `ft_metadata` using `LazyOption`](https://github.com/near/near-sdk-rs/blob/c2771af7fdfe01a4e8414046752ee16fb0d29d39/examples/fungible-token/ft/src/lib.rs#L71) as a reference example. \ No newline at end of file +MT contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of `approved_account_ids` for calls to `mt_*` methods other than `mt_tokens`. See `near-contract-standards` [implementation of `ft_metadata` using `LazyOption`](https://github.com/near/near-sdk-rs/blob/c2771af7fdfe01a4e8414046752ee16fb0d29d39/examples/fungible-token/ft/src/lib.rs#L71) as a reference example. \ No newline at end of file From fce6f7d976b12131c6c7986c82ebf79218ebafd0 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 22 Mar 2022 10:24:11 -0700 Subject: [PATCH 096/108] fix: update specs to use token_id vs. id to align with other specs --- specs/Standards/MultiToken/Core.md | 2 +- specs/Standards/MultiToken/Metadata.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index f2817c16d..9900c2ed5 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -56,7 +56,7 @@ To recap, we choose to create this standard, to improve interoperability, develo // extensions such as Approval Management, Enumeration, Metadata, or other // attributes may be included in this structure. type Token = { - id: string, + token_id: string, owner_id: string | null } diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index 48d42fe37..bf597eab6 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -81,7 +81,7 @@ A new attribute MUST be added to each `Token` struct: ```diff type Token = { - id: string, + token_id: string, + token_metadata?: MTTokenMetadata, + base_metadata_id: string, } From 7a1f8b25a52b1764d7265255eab1ab4eed0d7c7f Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Mon, 28 Mar 2022 14:27:30 -0700 Subject: [PATCH 097/108] fix: add new summary format for mt token spec --- neps/nep-0246.md | 550 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 550 insertions(+) create mode 100644 neps/nep-0246.md diff --git a/neps/nep-0246.md b/neps/nep-0246.md new file mode 100644 index 000000000..27f34f41c --- /dev/null +++ b/neps/nep-0246.md @@ -0,0 +1,550 @@ +--- +NEP: 246 +Title: Multi Token Standard +Author: Zane Starr , @riqi, @marcos.sun +DiscussionsTo: https://github.com/near/NEPs/discussions/246 +Status: Final +Type: Standards Track +Category: Contract +Created: 03-Mar-2022 +Requires: 297 +--- + +## Summary + +A standard interface for a multi token standard that supports fungible, semi-fungible,non-fungible, and tokens of any type, allowing for ownership, transfer, and batch transfer of tokens regardless of specific type. + +## Motivation + + +In the three years since [ERC-1155] was ratified by the Ethereum Community, Multi Token based contracts have proven themselves valuable assets. Many blockchain projects emulate this standard for representing multiple token assets classes in a single contract. The ability to reduce transaction overhead for marketplaces, video games, DAOs, and exchanges is appealing to the blockchain ecosystem and simplifies transactions for developers. + +Having a single contract represent NFTs, FTs, and tokens that sit inbetween greatly improves efficiency. The standard also introduced the ability to make batch requests with multiple asset classes reducing complexity. This standard allows operations that currently require _many_ transactions to be completed in a single transaction that can transfer not only NFTs and FTs, but any tokens that are a part of same token contract. + +With this standard, we have sought to take advantage of the ability of the NEAR blockchain to scale. Its sharded runtime, and [storage staking] model that decouples [gas] fees from storage demand, enables ultra low transaction fees and greater on chain storage ( see [Metadata] extension). + +With the aforementioned, it is noteworthy to mention that like the [NFT] standard the Multi Token standard, implements `mt_transfer_call`, +which allows, a user to attach many tokens to a call to a separate contract. Additionally, this standard includes an optional [Approval Management] extension. The extension allows marketplaces to trade on behalf of a user, providing additional flexibility for dApps. + +Prior art: + +- [ERC-721] +- [ERC-1155] +- [NEAR Fungible Token Standard][FT], which first pioneered the "transfer and call" technique +- [NEAR Non-Fungible Token Standard][NFT] + +## Rationale and alternatives + +Why have another standard, aren't fungible and non-fungible tokens enough? The current fungible token and non-fungible token standards, do not provide support for representing many FT tokens in a single contract, as well as the flexibility to define different token types with different behavior in a single contract. This is something that makes it difficult to be interoperable with other major blockchain networks, that implement standards that allow for representation of many different FT tokens in a single contract such as Ethereum. + +The standard here introduces a few concepts that evolve the original [ERC-1155] standard to have more utility, while maintaining the original flexibilty of the standard. So keeping that in mind, we are defining this as a new token type. It combines two main features of FT and NFT. It allows us to represent many token types in a single contract, and it's possible to store the amount for each token. + +The decision to not use FT and NFT as explict token types was taken to allow the community to define their own standards and meanings through metadata. As standards evolve on other networks, this specification allows the standard to be able to represent tokens across networks accurately, without necessarily restricting the behavior to any preset definition. + +The issues with this in general is a problem with defining what metadata means and how is that interpeted. We have chosen to follow the pattern that is currently in use on Ethereum in the [ERC-1155] standard. That pattern relies on people to make extensions or to make signals as to how they want the metadata to be represented for their use case. + +One of the areas that has broad sweeping implications from the [ERC-1155] standard is the lack of direct access to metadata. With Near's sharding we are able to have a [Metadata Extension](Metadata.md) for the standard that exists on chain. So developers and users are not required to use an indexer to understand, how to interact or interpet tokens, via token identifiers that they receive. + +Another extension that we made was to provide an explict ability for developers and users to group or link together series of NFTs/FTs or any combination of tokens. This provides additional flexiblity that the [ERC-1155] standard only has loose guidelines on. This was chosen to make it easy for consumers to understand the relationship between tokens within the contract. + +To recap, we choose to create this standard, to improve interoperability, developer ease of use, and to extend token representability beyond what was available directly in the FT or NFT standards. We believe this to be another tool in the developer's toolkit. It makes it possible to represent many types of tokens and to enable exchanges of many tokens within a single `transaction`. + +## Specification + +**NOTES**: +- All amounts, balances and allowance are limited by `U128` (max value `2**128 - 1`). +- Token standard uses JSON for serialization of arguments and results. +- Amounts in arguments and results are serialized as Base-10 strings, e.g. `"100"`. This is done to avoid JSON limitation of max integer value of `2**53`. +- The contract must track the change in storage when adding to and removing from collections. This is not included in this core multi token standard but instead in the [Storage Standard](../StorageManagement.md). +- To prevent the deployed contract from being modified or deleted, it should not have any access keys on its account. + +### 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. +type Token = { + token_id: string, + owner_id: string | null +} + +/******************/ +/* CHANGE METHODS */ +/******************/ + +// Simple transfer. Transfer a given `token_id` from current owner to +// `receiver_id`. +// +// Requirements +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes +// * Caller must have greater than or equal to the `amount` being requested +// * Contract MUST panic if called by someone other than token owner or, +// if using Approval Management, one of the approved accounts +// * `approval_id` is for use with Approval Management extension, see +// that document for full explanation. +// * If using Approval Management, contract MUST nullify approved accounts on +// successful transfer. +// +// Arguments: +// * `receiver_id`: the valid NEAR account receiving the 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_id`: 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, + approval_id: number|null, + memo: string|null, +) {} + +// Simple batch transfer. Transfer a given `token_ids` from current owner to +// `receiver_id`. +// +// Requirements +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes +// * Caller must have greater than or equal to the `amounts` being requested for the given `token_ids` +// * Contract MUST panic if called by someone other than token owner or, +// if using Approval Management, one of the approved accounts +// * `approval_id` is for use with Approval Management extension, see +// that document for full explanation. +// * If using Approval Management, contract MUST nullify approved accounts on +// successful transfer. +// * Contract MUST panic if called with the length of `token_ids` not equal to `amounts` is not equal +// * Contract MUST panic if `approval_ids` is not `null` and does not equal the length of `token_ids` +// +// Arguments: +// * `receiver_id`: the valid NEAR account receiving the token +// * `token_ids`: the tokens to 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. +// * `approval_ids`: expected approval IDs per `token_ids`. If a `token_id` does +// not have a corresponding approval id then the entry in the array must be marked null. +// The `approval_ids` are numbers 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[], + approval_ids: (number | null)[] | null, + memo: string|null, +) {} + + +// Transfer token and call a method on a receiver contract. A successful +// workflow will end in a success execution outcome to the callback on the MT +// contract at the method `mt_resolve_transfer`. +// +// You can think of this as being similar to attaching native NEAR tokens to a +// function call. It allows you to attach any Multi Token, token in a call to a +// receiver contract. +// +// Requirements: +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security +// purposes +// * Caller must have greater than or equal to the `amount` being requested +// * Contract MUST panic if called by someone other than token owner or, +// if using Approval Management, one of the approved accounts +// * The receiving contract must implement `mt_on_transfer` according to the +// standard. If it does not, MT contract's `mt_resolve_transfer` MUST deal +// with the resulting failed cross-contract call and roll back the transfer. +// * Contract MUST implement the behavior described in `mt_resolve_transfer` +// * `approval_id` is for use with Approval Management extension, see +// that document for full explanation. +// * If using Approval Management, contract MUST nullify approved accounts on +// successful transfer. +// +// Arguments: +// * `receiver_id`: the valid NEAR account receiving the token. +// * `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. +// * `approval_id`: 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. +// * `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, + approval_id: number|null, + memo: string|null, + msg: string, +): Promise {} + + + +// Transfer tokens and call a method on a receiver contract. A successful +// workflow will end in a success execution outcome to the callback on the MT +// contract at the method `mt_resolve_transfer`. +// +// You can think of this as being similar to attaching native NEAR tokens to a +// function call. It allows you to attach any Multi Token, token in a call to a +// receiver contract. +// +// Requirements: +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security +// purposes +// * Caller must have greater than or equal to the `amount` being requested +// * Contract MUST panic if called by someone other than token owner or, +// if using Approval Management, one of the approved accounts +// * The receiving contract must implement `mt_on_transfer` according to the +// standard. If it does not, MT contract's `mt_resolve_transfer` MUST deal +// with the resulting failed cross-contract call and roll back the transfer. +// * Contract MUST implement the behavior described in `mt_resolve_transfer` +// * `approval_id` is for use with Approval Management extension, see +// that document for full explanation. +// * If using Approval Management, contract MUST nullify approved accounts on +// successful transfer. +// * Contract MUST panic if called with the length of `token_ids` not equal to `amounts` is not equal +// * Contract MUST panic if `approval_ids` is not `null` and does not equal the length of `token_ids` +// +// Arguments: +// * `receiver_id`: the valid NEAR account receiving the token. +// * `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. +// * `approval_ids`: expected approval IDs per `token_ids`. If a `token_id` does +// not have a corresponding approval id then the entry in the array must be marked null. +// The `approval_ids` are numbers smaller than 2^53, and therefore representable as JSON. +// ApprovalId See Approval Management standard for full explanation. +// * `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[], + approval_ids: (number|null)[] | null, + memo: string|null, + msg: string, +): Promise {} + +/****************/ +/* VIEW METHODS */ +/****************/ + + +// Returns the tokens with the given `token_ids` or `null` if no such token. +function mt_token(token_ids: string[]) (Token | null)[] + +// Returns the balance of an account for the given `token_id`. +// The balance though wrapped in quotes and treated like a string, +// the number will be stored as an unsigned integer with 128 bits. +// Arguments: +// * `account_id`: the NEAR account that owns the token. +// * `token_id`: the token to retrieve the balance from +function mt_balance_of(account_id: string, token_id: string): string + +// Returns the balances of an account for the given `token_ids`. +// The balances though wrapped in quotes and treated like strings, +// the numbers will be stored as an unsigned integer with 128 bits. +// Arguments: +// * `account_id`: the NEAR account that owns the tokens. +// * `token_ids`: the tokens to retrieve the balance from +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. +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. +function mt_batch_supply(token_ids: string[]): (string | null)[] +``` + +The following behavior is required, but contract authors may name this function something other than the conventional `mt_resolve_transfer` used here. + +```ts +// Finalize an `mt_transfer_call` or `mt_batch_transfer_call` chain of cross-contract calls. Generically +// referred to as `mt_transfer_call` as it applies to `mt_batch_transfer_call` as well. +// +// The `mt_transfer_call` process: +// +// 1. Sender calls `mt_transfer_call` on MT contract +// 2. MT contract transfers token from sender to receiver +// 3. MT contract calls `mt_on_transfer` on receiver contract +// 4+. [receiver contract may make other cross-contract calls] +// N. MT contract resolves promise chain with `mt_resolve_transfer`, and may +// transfer token back to sender +// +// Requirements: +// * Contract MUST forbid calls to this function by any account except self +// * If promise chain failed, contract MUST revert token transfer +// * If promise chain resolves with `true`, contract MUST return token to +// `sender_id` +// +// Arguments: +// * `sender_id`: the sender of `mt_transfer_call` +// * `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` +// * `approved_token_ids`: if using Approval Management, contract MUST provide +// set of original approved accounts in this argument, and restore these +// approved accounts in case of revert. +// +// Returns total amount spent by the `receiver_id`, corresponding to the `token_id`. +// The amounts returned, though wrapped in quotes and treated like strings, +// the numbers will be stored as an unsigned integer with 128 bits. +// 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, + receiver_id: string, + token_ids: string[], + approved_account_ids: (null | string[])[] | null, +):string[] {} +``` + +### Receiver Interface + +Contracts which want to make use of `mt_transfer_call` and `mt_batch_transfer_call` must implement the following: + +```ts +// Take some action after receiving a multi token +// +// Requirements: +// * Contract MUST restrict calls to this function to a set of whitelisted +// contracts +// * Contract MUST panic if `token_ids` length does not equals `amounts` +// length +// * Contract MUST panic if `previous_owner_ids` length does not equals `token_ids` +// length +// +// Arguments: +// * `sender_id`: the sender of `mt_transfer_call` +// * `previous_owner_ids`: the account that owned the tokens prior to it being +// transferred to this contract, which can differ from `sender_id` if using +// Approval Management extension +// * `token_ids`: the `token_ids` argument given to `mt_transfer_call` +// * `amounts`: the `token_ids` argument given to `mt_transfer_call` +// * `msg`: information necessary for this contract to know how to process the +// request. This may include method names and/or arguments. +// +// Returns the number of unused tokens in string form. For instance, if `amounts` +// 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[], + token_ids: string[], + amounts: string[], + msg: string, +): Promise; +``` + +## Events + +NEAR and third-party applications need to track + `mint`, `burn`, `transfer` events for all MT-driven apps consistently. This exension addresses that. + +Note that applications, including NEAR Wallet, could require implementing additional methods to display tokens correctly such as [`mt_metadata`](Metadata.md) and [`mt_tokens_for_owner`](Enumeration.md). + +### Events Interface +Multi Token Events MUST have `standard` set to `"nep246"`, 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 relavant types: `MtMintLog[] | MtBurnLog[] | MtTransferLog[]`: + + + +```ts +interface MtEventLogData { + EVENT_JSON: { + standard: "nep246", + version: "1.0.0", + event: MtEvent, + data: MtMintLog[] | MtBurnLog[] | MtTransferLog[] + } +} +``` + +```ts +// Minting event log. Emitted when a token is minted/created. +// Requirements +// * Contract MUST emit event when minting a token +// Fields +// * Contract token_ids and amounts MUST be the same length +// * `owner_id`: the account receiving the minted token +// * `token_ids`: the tokens minted +// * `amounts`: the number of tokens minted, wrapped in quotes and treated +// like a string, although the numbers will be stored as an unsigned integer +// array with 128 bits. +// * `memo`: optional message +interface MtMintLog { + owner_id: string, + token_ids: string[], + amounts: string[], + memo?: string +} + +// Burning event log. Emitted when a token is burned. +// Requirements +// * Contract MUST emit event when minting a token +// Fields +// * Contract token_ids and amounts MUST be the same length +// * `owner_id`: the account whose token(s) are being burned +// * `authorized_id`: approved account_id to burn, if applicable +// * `token_ids`: the tokens being burned +// * `amounts`: the number of tokens burned, wrapped in quotes and treated +// like a string, although the numbers will be stored as an unsigned integer +// array with 128 bits. +// * `memo`: optional message +interface MtBurnLog { + owner_id: string, + authorized_id?: string, + token_ids: string[], + amounts: string[], + memo?: string +} + +// Transfer event log. Emitted when a token is transfered. +// Requirements +// * Contract MUST emit event when transferring a token +// Fields +// * `authorized_id`: approved account_id to transfer +// * `old_owner_id`: the account sending the tokens "sender.near" +// * `new_owner_id`: the account receiving the tokens "receiver.near" +// * `token_ids`: the tokens to transfer +// * `amounts`: the number of tokens to transfer, wrapped in quotes and treated +// like a string, although the numbers will be stored as an unsigned integer +// array with 128 bits. +interface MtTransferLog { + authorized_id?: string, + old_owner_id: string, + new_owner_id: string, + token_ids: string[], + amounts: string[], + memo?: string +} +``` + +## Examples + +Single owner minting (pretty-formatted for readability purposes): + +```js +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_mint", + "data": [ + {"owner_id": "foundation.near", "token_ids": ["aurora", "proximitylabs_ft"], "amounts":["1", "100"]} + ] +} +``` + +Different owners minting: + +```js +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_mint", + "data": [ + {"owner_id": "foundation.near", "token_ids": ["aurora", "proximitylabs_ft"], "amounts":["1","100"]}, + {"owner_id": "user1.near", "token_ids": ["meme"], "amounts": ["1"]} + ] +} +``` + +Different events (separate log entries): + +```js +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_burn", + "data": [ + {"owner_id": "foundation.near", "token_ids": ["aurora", "proximitylabs_ft"], "amounts": ["1","100"]}, + ] +} +``` + +Authorized id: + +```js +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_burn", + "data": [ + {"owner_id": "foundation.near", "token_ids": ["aurora_alpha", "proximitylabs_ft"], "amounts": ["1","100"], "authorized_id": "thirdparty.near" }, + ] +} +``` + +```js +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_transfer", + "data": [ + {"old_owner_id": "user1.near", "new_owner_id": "user2.near", "token_ids": ["meme"], "amounts":["1"], "memo": "have fun!"} + ] +} + +EVENT_JSON:{ + "standard": "nep246", + "version": "1.0.0", + "event": "mt_transfer", + "data": [ + {"old_owner_id": "user2.near", "new_owner_id": "user3.near", "token_ids": ["meme"], "amounts":["1"], "authorized_id": "thirdparty.near", "memo": "have fun!"} + ] +} +``` + +## Further Event Methods + +Note that the example events covered above cover two different kinds of events: +1. Events that are not specified in the MT Standard (`mt_mint`, `mt_burn`) +2. An event that is covered in the [Multi Token Core Standard](https://nomicon.io/Standards/MultiToken/Core.html#mt-interface). (`mt_transfer`) + +This event standard also applies beyond the three events highlighted here, where future events follow the same convention of as the second type. For instance, if an MT contract uses the [approval management standard](https://nomicon.io/Standards/MultiToken/ApprovalManagement.html), it may emit an event for `mt_approve` if that's deemed as important by the developer community. + +Please feel free to open pull requests for extending the events standard detailed here as needs arise. + +## Copyright +[copyright]: #copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). + + [ERC-721]: https://eips.ethereum.org/EIPS/eip-721 + [ERC-1155]: https://eips.ethereum.org/EIPS/eip-1155 + [storage staking]: https://docs.near.org/docs/concepts/storage-staking + [gas]: https://docs.near.org/docs/concepts/gas + [Metadata]: ../specs/Standards/MultiToken/Metadata.md + [NFT]: ../specs/Standards/NonFungibleToken/Core.md + [Approval Management]: ../specs/Standards/MultiToken/ApprovalManagement.md + [FT]: ../specs/Standards/FungibleToken/Core.md \ No newline at end of file From 9d9d00ab01096813e7ae5319b52b9ecb9f9cdcdb Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 29 Mar 2022 09:53:36 -0700 Subject: [PATCH 098/108] chore: refactor nep from 246 to 245 --- neps/{nep-0246.md => nep-0245.md} | 18 +++++++++--------- specs/Standards/MultiToken/Core.md | 2 +- specs/Standards/MultiToken/Enumeration.md | 2 +- specs/Standards/MultiToken/Events.md | 18 +++++++++--------- specs/Standards/MultiToken/Metadata.md | 4 ++-- 5 files changed, 22 insertions(+), 22 deletions(-) rename neps/{nep-0246.md => nep-0245.md} (99%) diff --git a/neps/nep-0246.md b/neps/nep-0245.md similarity index 99% rename from neps/nep-0246.md rename to neps/nep-0245.md index 27f34f41c..655c2d551 100644 --- a/neps/nep-0246.md +++ b/neps/nep-0245.md @@ -1,5 +1,5 @@ --- -NEP: 246 +NEP: 245 Title: Multi Token Standard Author: Zane Starr , @riqi, @marcos.sun DiscussionsTo: https://github.com/near/NEPs/discussions/246 @@ -375,14 +375,14 @@ 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`](Metadata.md) and [`mt_tokens_for_owner`](Enumeration.md). ### Events Interface -Multi Token Events MUST have `standard` set to `"nep246"`, 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 relavant types: `MtMintLog[] | MtBurnLog[] | MtTransferLog[]`: +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 relavant types: `MtMintLog[] | MtBurnLog[] | MtTransferLog[]`: ```ts interface MtEventLogData { EVENT_JSON: { - standard: "nep246", + standard: "nep245", version: "1.0.0", event: MtEvent, data: MtMintLog[] | MtBurnLog[] | MtTransferLog[] @@ -456,7 +456,7 @@ Single owner minting (pretty-formatted for readability purposes): ```js EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_mint", "data": [ @@ -469,7 +469,7 @@ Different owners minting: ```js EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_mint", "data": [ @@ -483,7 +483,7 @@ Different events (separate log entries): ```js EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_burn", "data": [ @@ -496,7 +496,7 @@ Authorized id: ```js EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_burn", "data": [ @@ -507,7 +507,7 @@ EVENT_JSON:{ ```js EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_transfer", "data": [ @@ -516,7 +516,7 @@ EVENT_JSON:{ } EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_transfer", "data": [ diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 9900c2ed5..65b600b86 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -1,4 +1,4 @@ -# Multi Token ([NEP-246](https://github.com/near/NEPs/discussions/246)) +# Multi Token ([NEP-245](https://github.com/near/NEPs/discussions/246)) Version `1.0.0` diff --git a/specs/Standards/MultiToken/Enumeration.md b/specs/Standards/MultiToken/Enumeration.md index 62c1e491b..894ac3687 100644 --- a/specs/Standards/MultiToken/Enumeration.md +++ b/specs/Standards/MultiToken/Enumeration.md @@ -1,4 +1,4 @@ -# Multi Token Enumeration([NEP-246](https://github.com/near/NEPs/discussions/246)) +# Multi Token Enumeration([NEP-245](https://github.com/near/NEPs/discussions/246)) Version `1.0.0` diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index 48c113351..23bf921e8 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -1,4 +1,4 @@ -# Multi Token Event([NEP-246](https://github.com/near/NEPs/discussions/246)) +# Multi Token Event([NEP-245](https://github.com/near/NEPs/discussions/246)) Version `1.0.0` @@ -16,14 +16,14 @@ 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`](Metadata.md) and [`mt_tokens_for_owner`](Enumeration.md). ## Interface -Multi Token Events MUST have `standard` set to `"nep246"`, 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 relavant types: `MtMintLog[] | MtBurnLog[] | MtTransferLog[]`: +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 relavant types: `MtMintLog[] | MtBurnLog[] | MtTransferLog[]`: ```ts interface MtEventLogData { EVENT_JSON: { - standard: "nep246", + standard: "nep245", version: "1.0.0", event: MtEvent, data: MtMintLog[] | MtBurnLog[] | MtTransferLog[] @@ -97,7 +97,7 @@ Single owner minting (pretty-formatted for readability purposes): ```js EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_mint", "data": [ @@ -110,7 +110,7 @@ Different owners minting: ```js EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_mint", "data": [ @@ -124,7 +124,7 @@ Different events (separate log entries): ```js EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_burn", "data": [ @@ -137,7 +137,7 @@ Authorized id: ```js EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_burn", "data": [ @@ -148,7 +148,7 @@ EVENT_JSON:{ ```js EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_transfer", "data": [ @@ -157,7 +157,7 @@ EVENT_JSON:{ } EVENT_JSON:{ - "standard": "nep246", + "standard": "nep245", "version": "1.0.0", "event": "mt_transfer", "data": [ diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index bf597eab6..dcf97cbd6 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -1,4 +1,4 @@ -# Multi Token Metadata([NEP-246](https://github.com/near/NEPs/discussions/246)) +# Multi Token Metadata([NEP-245](https://github.com/near/NEPs/discussions/246)) Version `1.0.0` @@ -20,7 +20,7 @@ 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: #246 +- Discussion about NEAR's complete Multi Token standard: #245 ## Interface From 414fdcdec5a2bdb4580ae3b1824e4bb1fe9e4b36 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Tue, 29 Mar 2022 09:56:18 -0700 Subject: [PATCH 099/108] chore: set status to draft --- neps/nep-0245.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 655c2d551..7b77d88ea 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -3,7 +3,7 @@ NEP: 245 Title: Multi Token Standard Author: Zane Starr , @riqi, @marcos.sun DiscussionsTo: https://github.com/near/NEPs/discussions/246 -Status: Final +Status: Draft Type: Standards Track Category: Contract Created: 03-Mar-2022 From dcbb0ed5217d6ae8ec0c07dde425fe9137675f44 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 30 Mar 2022 13:39:32 -0700 Subject: [PATCH 100/108] fix: refactor approvals to be iterative, vs included in Token. This change comes about because with Multi Token standard there may be many holders of a single token id. It may be gas prohibitive to return back all of the approval data for a token in the Token response. The solution was to introduce a new method called mt_token_approvals which allows one to iterate through, the list of token approvals for a specific token id. This is a minimal addition that allows consumers of the data to find all the approvals for a particular token id. --- .../MultiToken/ApprovalManagement.md | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index 9763d5043..c509c516d 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -1,4 +1,4 @@ -# Multi Token Standard Approval Management([NEP-246](https://github.com/near/NEPs/discussions/246)) +# Multi Token Standard Approval Management([NEP-245](https://github.com/near/NEPs/discussions/246)) Version `1.0.0` @@ -222,7 +222,7 @@ Again, note that no previous approvers will get cross-contract calls in this cas ## Reference-level explanation -The `Token` structure returned by `mt_tokens` must include an `approved_account_ids` field, which is a map of account IDs to `Approval`. The `amount` field though wrapped in quotes and treated like strings, the number will be stored as an unsigned integer with 128 bits. +The `TokenApproval` structure returned by `mt_token_approvals` returns `approved_account_ids` field, which is a map of account IDs to `Approval` and `owner_id` which is the associated account approved for removal from. The `amount` field though wrapped in quotes and treated like strings, the number will be stored as an unsigned integer with 128 bits. in approval is Using TypeScript's [Record type](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype) notation: ```diff @@ -230,18 +230,17 @@ The `Token` structure returned by `mt_tokens` must include an `approved_account_ + amount: string + approval_id: string + } - type Token = { - id: string, - owner_id: string, ++ type TokenApproval = { ++ owner_id: string, + approved_account_ids: Record, - }; ++ }; ``` -Example token data: +Example token approval data: ```json -{ - "id": "1", +[{ + "owner_id": "alice.near", "approved_account_ids": { "bob.near": { "amount": "100", @@ -252,7 +251,7 @@ Example token data: "approval_id": 2, } } -} +}] ``` ### What is an "approval ID"? @@ -273,7 +272,6 @@ Keeping with the example above, say the initial approval of the second marketpla ```json { - "id": "1", "owner_id": "alice.near", "approved_account_ids": { "marketplace_1.near": { @@ -291,7 +289,6 @@ But after the transfers and re-approval described above, the token might have `a ```json { - "id": "1", "owner_id": "alice.near", "approved_account_ids": { "marketplace_2.near": { @@ -415,6 +412,19 @@ function mt_is_approved( approval_ids: number[]|null ): boolean {} +// 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[] {} ``` From d03077649a5cc1eddba084d4a0c4244c39a32be9 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Wed, 30 Mar 2022 14:02:18 -0700 Subject: [PATCH 101/108] fix: add approval view logic for singular request --- specs/Standards/MultiToken/ApprovalManagement.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index c509c516d..525f4c2bc 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -412,6 +412,19 @@ function mt_is_approved( 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 check the existence of 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: @@ -425,7 +438,6 @@ function mt_token_approvals( from_index: string|null, // default: "0" limit: number|null, ): TokenApproval[] {} - ``` ### Why must `mt_approve` panic if `mt_revoke_all` would fail later? From d413ca3ab4fcaed868227412e69f614ff6338618 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 31 Mar 2022 14:25:45 -0700 Subject: [PATCH 102/108] fix: adjust mt_transfer* and mt_resolve to provide granular resolution Prior to this fix, we had singular representation for token_ids that may have many owners. There was no way to resolve approvals for those types of tokens, typically ft style tokens. This change links together the owners of the accounts to the ft. So when an approval occurs we can resolve which account to transfer the tokens from as well as handle resolution of transfer failures from a singular token id with many owners. --- neps/nep-0245.md | 65 +++++++++++++++++++++--------- specs/Standards/MultiToken/Core.md | 64 ++++++++++++++++++++--------- 2 files changed, 90 insertions(+), 39 deletions(-) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 7b77d88ea..2045481ad 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, @marcos.sun +Author: Zane Starr , @riqi, @jriemann, @marcos.sun DiscussionsTo: https://github.com/near/NEPs/discussions/246 Status: Draft Type: Standards Track @@ -92,9 +92,11 @@ type Token = { // * `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_id`: expected approval ID. A number smaller than +// * `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 -// standard for full explanation. +// standard for full explanation. // * `memo` (optional): for use cases that may benefit from indexing or // providing information for a transfer @@ -103,7 +105,7 @@ function mt_transfer( receiver_id: string, token_id: string, amount: string, - approval_id: number|null, + approval: [owner_id: string, approval_id: number]|null, memo: string|null, ) {} @@ -128,10 +130,14 @@ 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. -// * `approval_ids`: expected approval IDs per `token_ids`. If a `token_id` does -// not have a corresponding approval id then the entry in the array must be marked null. -// The `approval_ids` are numbers smaller than 2^53, and therefore representable as JSON. -// See Approval Management standard for full explanation. +// * `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. // * `memo` (optional): for use cases that may benefit from indexing or // providing information for a transfer @@ -140,7 +146,7 @@ function mt_batch_transfer( receiver_id: string, token_ids: string[], amounts: string[], - approval_ids: (number | null)[] | null, + approvals: ([owner_id: string, approval_id: number]| null)[]| null, memo: string|null, ) {} @@ -174,9 +180,11 @@ function mt_batch_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_id`: expected approval ID. A number smaller than +// * `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 -// standard for full explanation. // * `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 @@ -188,7 +196,7 @@ function mt_transfer_call( receiver_id: string, token_id: string, amount: string, - approval_id: number|null, + approval: [owner_id: string, approval_id: number]|null, memo: string|null, msg: string, ): Promise {} @@ -226,10 +234,14 @@ function mt_transfer_call( // * `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. -// * `approval_ids`: expected approval IDs per `token_ids`. If a `token_id` does -// not have a corresponding approval id then the entry in the array must be marked null. -// The `approval_ids` are numbers smaller than 2^53, and therefore representable as JSON. -// ApprovalId See Approval Management standard for full explanation. +// * `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. // * `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 @@ -241,7 +253,7 @@ function mt_batch_transfer_call( receiver_id: string, token_ids: string[], amounts: string[], - approval_ids: (number|null)[] | null, + approvals: ([owner_id: string, approval_id: number]|null)[] | null, memo: string|null, msg: string, ): Promise {} @@ -307,9 +319,22 @@ The following behavior is required, but contract authors may name this function // * `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` -// * `approved_token_ids`: if using Approval Management, contract MUST provide -// set of original approved accounts in this argument, and restore these +// * `approvals (optional)`: if using Approval Management, contract MUST provide +// set of original approvals in this argument, and restore the // approved accounts in case of revert. +// `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. +// `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. +// +// // // Returns total amount spent by the `receiver_id`, corresponding to the `token_id`. // The amounts returned, though wrapped in quotes and treated like strings, @@ -323,7 +348,7 @@ function mt_resolve_transfer( sender_id: string, receiver_id: string, token_ids: string[], - approved_account_ids: (null | string[])[] | null, + approvals: (null | [owner_id: string, approval_id: number, amount: string][]) []| null ):string[] {} ``` diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 65b600b86..540c76f29 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -60,6 +60,7 @@ type Token = { owner_id: string | null } + /******************/ /* CHANGE METHODS */ /******************/ @@ -83,9 +84,11 @@ type Token = { // * `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_id`: expected approval ID. A number smaller than +// * `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 -// standard for full explanation. +// standard for full explanation. // * `memo` (optional): for use cases that may benefit from indexing or // providing information for a transfer @@ -94,7 +97,7 @@ function mt_transfer( receiver_id: string, token_id: string, amount: string, - approval_id: number|null, + approval: [owner_id: string, approval_id: number]|null, memo: string|null, ) {} @@ -119,10 +122,14 @@ 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. -// * `approval_ids`: expected approval IDs per `token_ids`. If a `token_id` does -// not have a corresponding approval id then the entry in the array must be marked null. -// The `approval_ids` are numbers smaller than 2^53, and therefore representable as JSON. -// See Approval Management standard for full explanation. +// * `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. // * `memo` (optional): for use cases that may benefit from indexing or // providing information for a transfer @@ -131,7 +138,7 @@ function mt_batch_transfer( receiver_id: string, token_ids: string[], amounts: string[], - approval_ids: (number | null)[] | null, + approvals: ([owner_id: string, approval_id: number]| null)[]| null, memo: string|null, ) {} @@ -165,9 +172,11 @@ function mt_batch_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_id`: expected approval ID. A number smaller than +// * `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 -// standard for full explanation. // * `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 @@ -179,7 +188,7 @@ function mt_transfer_call( receiver_id: string, token_id: string, amount: string, - approval_id: number|null, + approval: [owner_id: string, approval_id: number]|null, memo: string|null, msg: string, ): Promise {} @@ -217,10 +226,14 @@ function mt_transfer_call( // * `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. -// * `approval_ids`: expected approval IDs per `token_ids`. If a `token_id` does -// not have a corresponding approval id then the entry in the array must be marked null. -// The `approval_ids` are numbers smaller than 2^53, and therefore representable as JSON. -// ApprovalId See Approval Management standard for full explanation. +// * `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. // * `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 @@ -232,7 +245,7 @@ function mt_batch_transfer_call( receiver_id: string, token_ids: string[], amounts: string[], - approval_ids: (number|null)[] | null, + approvals: ([owner_id: string, approval_id: number]|null)[] | null, memo: string|null, msg: string, ): Promise {} @@ -298,9 +311,22 @@ The following behavior is required, but contract authors may name this function // * `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` -// * `approved_token_ids`: if using Approval Management, contract MUST provide -// set of original approved accounts in this argument, and restore these +// * `approvals (optional)`: if using Approval Management, contract MUST provide +// set of original approvals in this argument, and restore the // approved accounts in case of revert. +// `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. +// `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. +// +// // // Returns total amount spent by the `receiver_id`, corresponding to the `token_id`. // The amounts returned, though wrapped in quotes and treated like strings, @@ -314,7 +340,7 @@ function mt_resolve_transfer( sender_id: string, receiver_id: string, token_ids: string[], - approved_account_ids: (null | string[])[] | null, + approvals: (null | [owner_id: string, approval_id: number, amount: string][]) []| null ):string[] {} ``` From c8a856ef01e9d294659792a376a26de2c54aabe0 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 31 Mar 2022 14:37:38 -0700 Subject: [PATCH 103/108] fix: add summary listing to main readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9ca5a1f6f..9772014ef 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Changes to the protocol specification and standards are called NEAR Enhancement |[0178](https://github.com/near/NEPs/blob/master/neps/nep-0178.md) | Non Fungible Token Approval Management | @chadoh @thor314 | Final | |[0181](https://github.com/near/NEPs/blob/master/neps/nep-0181.md) | Non Fungible Token Enumeration | @chadoh @thor314 | Final | |[0199](https://github.com/near/NEPs/blob/master/neps/nep-0199.md) | Non Fungible Token Royalties and Payouts | @thor314 @mattlockyer | Final | +|[0245](https://github.com/near/NEPs/blob/master/neps/nep-0245.md) | Multi Token Standard | @zcstarr @riqi @jriemann @marcos.sun | Draft | |[0297](https://github.com/near/NEPs/blob/master/neps/nep-0297.md) | Contract Events Standard | @telezhnaya | Final | |[0330](https://github.com/near/NEPs/blob/master/neps/nep-0330.md) | Contract Metadata | @BenKurrek | Review | From 65af2d187f38ff5761343afeb03d6560725da8ae Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 31 Mar 2022 14:45:23 -0700 Subject: [PATCH 104/108] fix: refactor approval owner_id to approval_owner_id --- specs/Standards/MultiToken/ApprovalManagement.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index 525f4c2bc..5e571a21f 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -222,7 +222,7 @@ Again, note that no previous approvers will get cross-contract calls in this cas ## Reference-level explanation -The `TokenApproval` structure returned by `mt_token_approvals` returns `approved_account_ids` field, which is a map of account IDs to `Approval` and `owner_id` which is the associated account approved for removal from. The `amount` field though wrapped in quotes and treated like strings, the number will be stored as an unsigned integer with 128 bits. +The `TokenApproval` structure returned by `mt_token_approvals` returns `approved_account_ids` field, which is a map of account IDs to `Approval` and `approval_owner_id` which is the associated account approved for removal from. The `amount` field though wrapped in quotes and treated like strings, the number will be stored as an unsigned integer with 128 bits. in approval is Using TypeScript's [Record type](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype) notation: ```diff @@ -230,8 +230,9 @@ The `TokenApproval` structure returned by `mt_token_approvals` returns `approved + amount: string + approval_id: string + } ++ + type TokenApproval = { -+ owner_id: string, ++ approval_owner_id: string, + approved_account_ids: Record, + }; ``` @@ -240,7 +241,7 @@ Example token approval data: ```json [{ - "owner_id": "alice.near", + "approval_owner_id": "alice.near", "approved_account_ids": { "bob.near": { "amount": "100", @@ -272,7 +273,7 @@ Keeping with the example above, say the initial approval of the second marketpla ```json { - "owner_id": "alice.near", + "approval_owner_id": "alice.near", "approved_account_ids": { "marketplace_1.near": { "approval_id": 1, @@ -289,7 +290,7 @@ But after the transfers and re-approval described above, the token might have `a ```json { - "owner_id": "alice.near", + "approval_owner_id": "alice.near", "approved_account_ids": { "marketplace_2.near": { "approval_id": 3, @@ -416,7 +417,7 @@ function mt_is_approved( // // Arguments: // * `token_id`: the token for which to check an approval -// * `account_id`: the account to check the existence of approvals for +// * `account_id`: the account to retrieve approvals for // // Returns a TokenApproval object, as described in Approval Management standard function mt_token_approval( From 86de2f8394b917597efa8b9ff9d679a9712580cf Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Thu, 31 Mar 2022 16:43:24 -0700 Subject: [PATCH 105/108] fix: add reference current reference implementation locations --- neps/nep-0245.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 2045481ad..9c9f56f53 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -560,6 +560,14 @@ This event standard also applies beyond the three events highlighted here, where Please feel free to open pull requests for extending the events standard detailed here as needs arise. +## Reference Implementation + +[Minimum Viable Interface](https://github.com/tonic-foundation/nep-246/blob/master/src/multi_token/core/mod.rs) + +[MT Implementation](https://github.com/tonic-foundation/nep-246/blob/master/src/multi_token/core/core_impl.rs) + + + ## Copyright [copyright]: #copyright From 2568f906edc1ea943707ddcb87a881590303dfd3 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 1 Apr 2022 20:07:34 -0700 Subject: [PATCH 106/108] chore: add caution warning label to specification --- specs/Standards/MultiToken/ApprovalManagement.md | 4 ++++ specs/Standards/MultiToken/Core.md | 4 ++++ specs/Standards/MultiToken/Enumeration.md | 3 +++ specs/Standards/MultiToken/Events.md | 3 +++ specs/Standards/MultiToken/Metadata.md | 3 +++ 5 files changed, 17 insertions(+) diff --git a/specs/Standards/MultiToken/ApprovalManagement.md b/specs/Standards/MultiToken/ApprovalManagement.md index 5e571a21f..11420f76e 100644 --- a/specs/Standards/MultiToken/ApprovalManagement.md +++ b/specs/Standards/MultiToken/ApprovalManagement.md @@ -1,5 +1,9 @@ # Multi Token Standard Approval Management([NEP-245](https://github.com/near/NEPs/discussions/246)) +:::caution +This is part of the proposed spec [NEP-245](https://github.com/near/NEPs/blob/master/neps/nep-0245.md) and is subject to change. +::: + Version `1.0.0` diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 540c76f29..20c345b46 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -1,5 +1,9 @@ # Multi Token ([NEP-245](https://github.com/near/NEPs/discussions/246)) +:::caution +This is part of the proposed spec [NEP-245](https://github.com/near/NEPs/blob/master/neps/nep-0245.md) and is subject to change. +::: + Version `1.0.0` ## Summary diff --git a/specs/Standards/MultiToken/Enumeration.md b/specs/Standards/MultiToken/Enumeration.md index 894ac3687..7a0b560ba 100644 --- a/specs/Standards/MultiToken/Enumeration.md +++ b/specs/Standards/MultiToken/Enumeration.md @@ -1,5 +1,8 @@ # Multi Token Enumeration([NEP-245](https://github.com/near/NEPs/discussions/246)) +:::caution +This is part of the proposed spec [NEP-245](https://github.com/near/NEPs/blob/master/neps/nep-0245.md) and is subject to change. +::: Version `1.0.0` diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index 23bf921e8..b13d4d05b 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -1,5 +1,8 @@ # Multi Token Event([NEP-245](https://github.com/near/NEPs/discussions/246)) +:::caution +This is part of the proposed spec [NEP-245](https://github.com/near/NEPs/blob/master/neps/nep-0245.md) and is subject to change. +::: Version `1.0.0` diff --git a/specs/Standards/MultiToken/Metadata.md b/specs/Standards/MultiToken/Metadata.md index dcf97cbd6..09b6237ea 100644 --- a/specs/Standards/MultiToken/Metadata.md +++ b/specs/Standards/MultiToken/Metadata.md @@ -1,5 +1,8 @@ # Multi Token Metadata([NEP-245](https://github.com/near/NEPs/discussions/246)) +:::caution +This is part of the proposed spec [NEP-245](https://github.com/near/NEPs/blob/master/neps/nep-0245.md) and is subject to change. +::: Version `1.0.0` From c7c9d145eae6d1b1247008a5a247ab8b3bc1dac0 Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Fri, 1 Apr 2022 20:14:30 -0700 Subject: [PATCH 107/108] chore: update links to latest reference implementation --- neps/nep-0245.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 9c9f56f53..71c246dd5 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -562,9 +562,9 @@ Please feel free to open pull requests for extending the events standard detaile ## Reference Implementation -[Minimum Viable Interface](https://github.com/tonic-foundation/nep-246/blob/master/src/multi_token/core/mod.rs) +[Minimum Viable Interface](https://github.com/jriemann/near-sdk-rs/blob/multi-token-reference-impl/near-contract-standards/src/multi_token/core/mod.rs) -[MT Implementation](https://github.com/tonic-foundation/nep-246/blob/master/src/multi_token/core/core_impl.rs) +[MT Implementation](https://github.com/jriemann/near-sdk-rs/blob/multi-token-reference-impl/near-contract-standards/src/multi_token/core/core_impl.rs) From 763b7d6ae529f60cd7dfbb86b6265d6a9dc82aca Mon Sep 17 00:00:00 2001 From: Zane Starr Date: Sat, 2 Apr 2022 09:58:03 -0700 Subject: [PATCH 108/108] chore: correct typos --- neps/nep-0245.md | 12 ++++++------ specs/Standards/MultiToken/Core.md | 12 ++++++------ specs/Standards/MultiToken/Events.md | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 71c246dd5..a9247bd3b 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -37,15 +37,15 @@ Prior art: Why have another standard, aren't fungible and non-fungible tokens enough? The current fungible token and non-fungible token standards, do not provide support for representing many FT tokens in a single contract, as well as the flexibility to define different token types with different behavior in a single contract. This is something that makes it difficult to be interoperable with other major blockchain networks, that implement standards that allow for representation of many different FT tokens in a single contract such as Ethereum. -The standard here introduces a few concepts that evolve the original [ERC-1155] standard to have more utility, while maintaining the original flexibilty of the standard. So keeping that in mind, we are defining this as a new token type. It combines two main features of FT and NFT. It allows us to represent many token types in a single contract, and it's possible to store the amount for each token. +The standard here introduces a few concepts that evolve the original [ERC-1155] standard to have more utility, while maintaining the original flexibility of the standard. So keeping that in mind, we are defining this as a new token type. It combines two main features of FT and NFT. It allows us to represent many token types in a single contract, and it's possible to store the amount for each token. -The decision to not use FT and NFT as explict token types was taken to allow the community to define their own standards and meanings through metadata. As standards evolve on other networks, this specification allows the standard to be able to represent tokens across networks accurately, without necessarily restricting the behavior to any preset definition. +The decision to not use FT and NFT as explicit token types was taken to allow the community to define their own standards and meanings through metadata. As standards evolve on other networks, this specification allows the standard to be able to represent tokens across networks accurately, without necessarily restricting the behavior to any preset definition. -The issues with this in general is a problem with defining what metadata means and how is that interpeted. We have chosen to follow the pattern that is currently in use on Ethereum in the [ERC-1155] standard. That pattern relies on people to make extensions or to make signals as to how they want the metadata to be represented for their use case. +The issues with this in general is a problem with defining what metadata means and how is that interpreted. We have chosen to follow the pattern that is currently in use on Ethereum in the [ERC-1155] standard. That pattern relies on people to make extensions or to make signals as to how they want the metadata to be represented for their use case. -One of the areas that has broad sweeping implications from the [ERC-1155] standard is the lack of direct access to metadata. With Near's sharding we are able to have a [Metadata Extension](Metadata.md) for the standard that exists on chain. So developers and users are not required to use an indexer to understand, how to interact or interpet tokens, via token identifiers that they receive. +One of the areas that has broad sweeping implications from the [ERC-1155] standard is the lack of direct access to metadata. With Near's sharding we are able to have a [Metadata Extension](Metadata.md) for the standard that exists on chain. So developers and users are not required to use an indexer to understand, how to interact or interpret tokens, via token identifiers that they receive. -Another extension that we made was to provide an explict ability for developers and users to group or link together series of NFTs/FTs or any combination of tokens. This provides additional flexiblity that the [ERC-1155] standard only has loose guidelines on. This was chosen to make it easy for consumers to understand the relationship between tokens within the contract. +Another extension that we made was to provide an explicit ability for developers and users to group or link together series of NFTs/FTs or any combination of tokens. This provides additional flexiblity that the [ERC-1155] standard only has loose guidelines on. This was chosen to make it easy for consumers to understand the relationship between tokens within the contract. To recap, we choose to create this standard, to improve interoperability, developer ease of use, and to extend token representability beyond what was available directly in the FT or NFT standards. We believe this to be another tool in the developer's toolkit. It makes it possible to represent many types of tokens and to enable exchanges of many tokens within a single `transaction`. @@ -454,7 +454,7 @@ interface MtBurnLog { memo?: string } -// Transfer event log. Emitted when a token is transfered. +// Transfer event log. Emitted when a token is transferred. // Requirements // * Contract MUST emit event when transferring a token // Fields diff --git a/specs/Standards/MultiToken/Core.md b/specs/Standards/MultiToken/Core.md index 20c345b46..8a1770d83 100644 --- a/specs/Standards/MultiToken/Core.md +++ b/specs/Standards/MultiToken/Core.md @@ -15,7 +15,7 @@ A standard interface for a multi token standard that supports fungible, semi-fun In the three years since [ERC-1155] was ratified by the Ethereum Community, Multi Token based contracts have proven themselves valuable assets. Many blockchain projects emulate this standard for representing multiple token assets classes in a single contract. The ability to reduce transaction overhead for marketplaces, video games, DAOs, and exchanges is appealing to the blockchain ecosystem and simplifies transactions for developers. -Having a single contract represent NFTs, FTs, and tokens that sit inbetween greatly improves efficiency. The standard also introduced the ability to make batch requests with multiple asset classes reducing complexity. This standard allows operations that currently require _many_ transactions to be completed in a single transaction that can transfer not only NFTs and FTs, but any tokens that are a part of same token contract. +Having a single contract represent NFTs, FTs, and tokens that sit in-between greatly improves efficiency. The standard also introduced the ability to make batch requests with multiple asset classes reducing complexity. This standard allows operations that currently require _many_ transactions to be completed in a single transaction that can transfer not only NFTs and FTs, but any tokens that are a part of same token contract. With this standard, we have sought to take advantage of the ability of the NEAR blockchain to scale. Its sharded runtime, and [storage staking] model that decouples [gas] fees from storage demand, enables ultra low transaction fees and greater on chain storage ( see [Metadata] extension). @@ -32,15 +32,15 @@ Prior art: Why have another standard, aren't fungible and non-fungible tokens enough? The current fungible token and non-fungible token standards, do not provide support for representing many FT tokens in a single contract, as well as the flexibility to define different token types with different behavior in a single contract. This is something that makes it difficult to be interoperable with other major blockchain networks, that implement standards that allow for representation of many different FT tokens in a single contract such as Ethereum. -The standard here introduces a few concepts that evolve the original [ERC-1155] standard to have more utility, while maintaining the original flexibilty of the standard. So keeping that in mind, we are defining this as a new token type. It combines two main features of FT and NFT. It allows us to represent many token types in a single contract, and it's possible to store the amount for each token. +The standard here introduces a few concepts that evolve the original [ERC-1155] standard to have more utility, while maintaining the original flexibility of the standard. So keeping that in mind, we are defining this as a new token type. It combines two main features of FT and NFT. It allows us to represent many token types in a single contract, and it's possible to store the amount for each token. -The decision to not use FT and NFT as explict token types was taken to allow the community to define their own standards and meanings through metadata. As standards evolve on other networks, this specification allows the standard to be able to represent tokens across networks accurately, without necessarily restricting the behavior to any preset definition. +The decision to not use FT and NFT as explicit token types was taken to allow the community to define their own standards and meanings through metadata. As standards evolve on other networks, this specification allows the standard to be able to represent tokens across networks accurately, without necessarily restricting the behavior to any preset definition. -The issues with this in general is a problem with defining what metadata means and how is that interpeted. We have chosen to follow the pattern that is currently in use on Ethereum in the [ERC-1155] standard. That pattern relies on people to make extensions or to make signals as to how they want the metadata to be represented for their use case. +The issues with this in general is a problem with defining what metadata means and how is that interpreted. We have chosen to follow the pattern that is currently in use on Ethereum in the [ERC-1155] standard. That pattern relies on people to make extensions or to make signals as to how they want the metadata to be represented for their use case. -One of the areas that has broad sweeping implications from the [ERC-1155] standard is the lack of direct access to metadata. With Near's sharding we are able to have a [Metadata Extension](Metadata.md) for the standard that exists on chain. So developers and users are not required to use an indexer to understand, how to interact or interpet tokens, via token identifiers that they receive. +One of the areas that has broad sweeping implications from the [ERC-1155] standard is the lack of direct access to metadata. With Near's sharding we are able to have a [Metadata Extension](Metadata.md) for the standard that exists on chain. So developers and users are not required to use an indexer to understand, how to interact or interpret tokens, via token identifiers that they receive. -Another extension that we made was to provide an explict ability for developers and users to group or link together series of NFTs/FTs or any combination of tokens. This provides additional flexiblity that the [ERC-1155] standard only has loose guidelines on. This was chosen to make it easy for consumers to understand the relationship between tokens within the contract. +Another extension that we made was to provide an explicit ability for developers and users to group or link together series of NFTs/FTs or any combination of tokens. This provides additional flexiblity that the [ERC-1155] standard only has loose guidelines on. This was chosen to make it easy for consumers to understand the relationship between tokens within the contract. To recap, we choose to create this standard, to improve interoperability, developer ease of use, and to extend token representability beyond what was available directly in the FT or NFT standards. We believe this to be another tool in the developer's toolkit. It makes it possible to represent many types of tokens and to enable exchanges of many tokens within a single `transaction`. diff --git a/specs/Standards/MultiToken/Events.md b/specs/Standards/MultiToken/Events.md index b13d4d05b..3076b5b85 100644 --- a/specs/Standards/MultiToken/Events.md +++ b/specs/Standards/MultiToken/Events.md @@ -73,7 +73,7 @@ interface MtBurnLog { memo?: string } -// Transfer event log. Emitted when a token is transfered. +// Transfer event log. Emitted when a token is transferred. // Requirements // * Contract MUST emit event when transferring a token // Fields