near / NEPs

The Near Enhancement Proposals repository
https://nomicon.io
204 stars 135 forks source link

Multi Token Contract Standard #246

Open zcstarr opened 3 years ago

zcstarr commented 3 years ago

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. This standard aims to make it possible to represent these multi tokens in a way that allows these tokens to be represented across chains.

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, transaction to a single transaction to trade around both NFTs and FTs that are a part of same token contract. Moreover, this flexibility has led to building proper infrastructure for ecosystems, that often have to issue many tokens under management such as games.

Finally on other chains, such as Ethereum it would be nice to be able to bridge a token contract from near to ETH or to other chains. Having a multi token standard that can represent many different classes of tokens is highly desirable

Background

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<String>,
    );

    /// 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<String>,
        msg: String,
    ) -> PromiseOrValue<U128>;

    /// 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<TokenId>,
        amounts: Vec<U128>,
        memo: Option<String>,
    );
    /// 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<TokenId>,
        amounts: Vec<U128>,
        memo: Option<String>,
        msg: String,
    ) -> PromiseOrValue<Vec<U128>>;

    /// 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<TokenId>) -> Vec<U128>;

    /// 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<TokenId>) -> Vec<U128>;
}

Receiver Trait

Notes

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: String,            // required, ex. "MOSIAC"
    pub icon: Option<String>,      // Data URL
    pub base_uri: Option<String>, // 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<String>, // URL to a JSON file with more info
    pub reference_hash: Option<Base64VecU8>, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included.
}

pub struct MultiTokenExtraMetadata {
    pub title: Option<String>, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055"
    pub description: Option<String>, // free-form description
    pub media: Option<String>, // URL to associated media, preferably to decentralized, content-addressed storage
    pub media_hash: Option<Base64VecU8>, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included.
    pub copies: Option<u64>, // number of copies of this set of metadata in existence when token was minted.
    pub issued_at: Option<String>, // ISO 8601 datetime when token was issued or minted
    pub expires_at: Option<String>, // ISO 8601 datetime when token expires
    pub starts_at: Option<String>, // ISO 8601 datetime when token starts being valid
    pub updated_at: Option<String>, // ISO 8601 datetime when token was last updated
    pub extra: Option<String>, // anything extra the NFT wants to store on-chain. Can be stringified JSON.
    pub reference: Option<String>, // URL to an off-chain JSON file with more info.
    pub reference_hash: Option<Base64VecU8>, // 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;
}

PR #245

Lev-Stambler commented 2 years ago

Wait I need this and would love for this to come along!

jorgeantonio21 commented 2 years ago

Hey guys, could you provide updates on the status of the Multi Token Standard? This is a very much useful feature to have for the Near dev toolkit ! Thanks ! :)

alexastrum commented 1 year ago

Hi, @zcstarr, @riqi, @jriemann, @marco-sundsk, Is anyone from this list still interested in co-authoring?

I want to take over authorship and push to finalize this spec and its standard implementation.

alexastrum commented 1 year ago

Hi @mikedotexe, @volovyks, This PR shows Mike as the designated Reviewer for this NEP. Are you guys ok to review and push this into the Final Call, or is there someone else you'd assign to do this instead?

There are a few bugs in this NEP that I found when reviewing it with my friend @uncle-T0ny. We'd like to fix them and propose an implementation to be included in NEAR standards.

frol commented 1 year ago

@alexastrum It would be awesome if you could sum up what work is left to be done. I see this draft implementation for near-sdk-rs: https://github.com/near/near-sdk-rs/pull/776

zcstarr commented 1 year ago

@

Hi @mikedotexe, @volovyks, This PR shows Mike as the designated Reviewer for this NEP. Are you guys ok to review and push this into the Final Call, or is there someone else you'd assign to do this instead?

There are a few bugs in this NEP that I found when reviewing it with my friend @uncle-T0ny. We'd like to fix them and propose an implementation to be included in NEAR standards.

@alexastrum and @uncle-T0ny, would be great if you would post what you found as issues to the PR for the spec. or drop it here to open the convo up. I'd be curious to know what you hit. and would probably be in line with the new review process @frol ?

The original PR by @jriemann was done, but was unable to be merged in due to lack of time for anyone to actually review it.

uncle-T0ny commented 1 year ago

The list of the issues that I found:

  1. There is an approval invalidation issue: after transfer with approve, granted approve not become invalid, and grantee can make the transfers with the same approval again. Also, it's with the current reference implementation it's not possible to make a transfer on an amount, less than the approved.
  2. by spec, we have a method
    function mt_is_approved(
    token_ids: [string],
    approved_account_id: string,
    amounts: [string],
    approval_ids: number[]|null
    ): boolean {}

    and in the current implementation owner_id (an account that granted approval) we get from env::predecessor_account_id(), which means the method can't be viewable. As a solution, we should update the spec, and add owner_id param to the args.

  3. Storage management is not implemented for MT standard, to store tokens_per_owner, we need to cover the contract storage and (Storage Management spec NEP-145)[https://nomicon.io/Standards/StorageManagement] can be an appropriate solution. It means, before the transfer,a client should check the available balance by viewing storage_balance_of, and perform storage_deposit if required.

Proposals:

Based on https://github.com/near/near-sdk-rs/pull/776 we're working on an updated version of the
ref. implementation that includes: fixes of mentioned above issues, near-sdk-rs updates with the relevant fixes, added more tests.

We would like to finalize this reference implementation and help the near community to receive the new standard. As soon as possible

frol commented 1 year ago

I want to take over authorship and push to finalize this spec and its standard implementation.

@alexastrum I think you can go ahead and prepare a PR, and this way we will continue the evolution of the standard. The best way to collaborate is to provide context and suggest solutions.

This PR shows Mike as the designated Reviewer for this NEP. Are you guys ok to review and push this into the Final Call, or is there someone else you'd assign to do this instead?

@near/wg-contract-standards will do the final review, but before we get there, the hope is to see a healthy discussion driven by the stakeholders of this standard.

uncle-T0ny commented 1 year ago

@frol, @zcstarr

This PR https://github.com/near/near-sdk-rs/pull/950 is based on https://github.com/near/near-sdk-rs/pull/776 with the updated codebase and the improvements.

The improvements list (by commits): 1) https://github.com/near/near-sdk-rs/pull/950/commits/6d79045ba304ed745fdcf102faf9f581309a502f

We used https://github.com/near/near-sdk-rs/pull/776 but in our forked near-sdk-rs because we needed to use the up-to-date codebase.

2) https://github.com/near/near-sdk-rs/pull/950/commits/c22daf61bec86656c371b01e23fac2ca607970bd

3) https://github.com/near/near-sdk-rs/pull/950/commits/e7cabb4734ecc6a6983b8f2317214ca821dc9ae1

4) https://github.com/near/near-sdk-rs/pull/950/commits/438ec7ac3213e899df50dcafd7cdea33f730efc9

5) https://github.com/near/near-sdk-rs/pull/950/commits/8ff8d46d0427515ae4975571c4c345061722251f

Could you please give us some advice on how to continue the MT finalization in the best way?

blasrodri commented 1 year ago

As part of our bridge using IBC, we definitely need this standard to be merged. @frol

frol commented 1 year ago

(I am cross-posting this message for visibility from here: https://github.com/near/NEPs/pull/245#issuecomment-1351844674)

Hey folks, I want to give an update here as it seems that the work regarding this NEP is not visible in this PR.

Thanks to @uncle-T0ny, there is a solid PR with the implementation in near-sdk-rs (https://github.com/near/near-sdk-rs/pull/950). The implementation was partially reviewed and @uncle-T0ny took it offline to test this implementation in his own contract before proceeding with submitting a NEP extension with the identified shortcomings to the current MT standard. Feel free to review the implementation and test it out!

joshuajbouw commented 1 year ago

Excellent, we will be checking this out next quarter as we would like to help get this going and finalised. Will be playing around with it then.

marco-sundsk commented 11 months ago

Now, we have a revision of this standard. This time, we focus on the combination of FT and NFT in those core functions cause we noticed some misunderstandings on FT and NFT workflow on near network in those interfaces. Beside that, we also make a supplement to the nep-0245.md to include all the extensions while leaving the one in specs untouched as a comparision base point, so that you could have a clear view about what have been changed in this revision.
To save your breath, we have a summary here to outline those key modifications and reasons behind them.

Now, the plan is to have an agreement on nep-0245.md first, then the corresponding fix would be applied to those in specs. And the standard code implementation would be the last.