Title: ERC_6909_multi_token_standard
Category: ERCs

Author: jtriley-eth
Date: 2023-04-19T17:46:40.162Z

eip: 6909
title: Multi-Token Standard
description: A minimal specification for managing multiple tokens by their id in a single contract.
author: Joshua Trujillo (@jtriley)
discussions-to: Add EIP: Minimal Multi-Token Interface by jtriley-eth · Pull Request #6909 · ethereum/EIPs · GitHub
status: Draft
type: Standards Track
category: ERC
created: 2023-04-19
requires: 165
*Edit: Removing full EIP to avoid duplicate updates. See PR for most recent changes.

Author: axe
Date: 2023-04-19T18:18:59.228Z

I really like proposed simplified token standard, which aims to address some of the complexities found in the ERC-1155 standard.

ERC6909Metadata

Do you see any utility of providing a standard way of changing metadata URI?
I think this can be a good solution: Metadata Standards
event MetadataUpdate(uint256 _tokenId);

Author: RenanSouza2
Date: 2023-04-19T19:21:52.401Z

1 - there is not a ‘getOperator’ function, what is the reason of it’s removal?
2 - Would you be willing to treat tokenId as bytes32 instead of uint256? this was always a pet peeve I had with ERC721 and ERC1155. The bad side would be less backwards compatibility.
ps: it is not good to copy the whole proposal here because it can get outdated compared to the one in github

Author: jtriley-eth
Date: 2023-04-19T19:37:10.048Z

This was an oversight, just added isOperator to the specification.
I would prefer uint256 for the sake of compatibility, are there specific advantages to bytes32 over uint256?
Updated, thank you!

Author: jtriley-eth
Date: 2023-04-19T19:49:02.711Z

It looks like OpenSea supports either the EIP-4906 MetadataUpdate(uint256) and BatchMetadataUpdate(uint256,uint256) events or the EIP-1155 URI(uint256) event.
The metadata extension currently doesn’t specify a URI update event, but I would favor URI(uint256) since the spec is close to EIP-1155 as-is.

Author: RenanSouza2
Date: 2023-04-19T20:03:15.342Z

Not many reasons to change from uint256 to bytes32 tbh, I just with the original protocols used bytes32 because it makes more sense as identifiers. Totally agree with you in keeping the compatibility.
Another thing that just came to my mint, Neither ERC20 nor ERC1155 required decimals to be implemented. Was this change intentional?

Author: jtriley-eth
Date: 2023-04-19T22:52:22.795Z

Just noticed ERC-20 requires it only in the metadata extension, but ERC-1155 only mentions it in the metadata spec.
I think it would make sense to require the decimals method in the metadata extension since it defaults to one (10 ** 0), so there’s no harm in implementing the method and not explicitly setting decimals for each token id.
Thoughts?

Author: RenanSouza2
Date: 2023-04-19T23:19:54.475Z

It is a great change, it makes decimals reliable enought defi protocols can use it and de default implementation is easy.
maybe talk about it in the rationale

Author: zachobront
Date: 2023-04-20T01:25:55.618Z

This is great, strong support for the idea of getting the bloat out of 1155.
I don’t love that decimals “SHOULD be ignored if not explicitly set to a non-zero value.” It’s not clear when decimals for each ID will be set, but seems likely to cause downstream problems that non-existent IDs will simply return 0, which could be used to creatively break share accounting.
I would recommend that either:
a) decimals is a consistent value across all ids on a given contract
b) the decimals mapping isn’t public, and instead uses a getter function that reverts if it’s set to zero

Author: RenanSouza2
Date: 2023-04-20T01:46:48.225Z

A contract can see the totalSupply for that tokenId and only care for the decimals if there is any token
the moment decimals is set is up to the implementation
setting a non zero decimal number would hurt the ability to represent fingible and non fungible tokens in the same contract

Author: jtriley-eth
Date: 2023-04-20T02:08:09.312Z

I like the idea of removing “SHOULD be ignored …” terminology, though I would argue against solution “a” since this may be used to wrap multiple assets of varying decimal amounts and  “b” because this would require contracts to use try/catch syntax for a view method on chain. The tokenURI being fallible is a product of ERC-721’s behavior, though decimals are more often queried on-chain than token uri’s.
Also, even if the decimals should never be ignored, querying decimals for an asset that have not been explicitly set will yield zero, which resolves to n * 10 ** 0 or n * 1.

Author: RenanSouza2
Date: 2023-04-20T10:53:30.477Z

Hey,
the interfaceId needs to be updated to 0x3b97451a if my calculation is correct
and what do you think about internal _mint and _burn in the Reference Implementation?

Author: jtriley-eth
Date: 2023-04-24T16:57:31.345Z

I computed a different interfaceId, 0xb2e69f8a.
Also, I added the internal mint and burn logic to the reference!

Author: RenanSouza2
Date: 2023-04-24T17:25:23.946Z

I saw your commit and your value is correct, when I calculated again it matched with yours

Author: fubuloubu
Date: 2023-05-04T13:55:33.409Z

Really like this standard, and appreciate the usage of YAML definitions 
My one comment is that the token URI functionality should be a separate extension from the regular token metadata, for cases where it makes sense to define normal metadata (e.g. Give the the token a name, symbol and decimals) but not URI metadata (because the different IDs represent something semi-fungible such as tokens with different classes of rights or a progressive unlock)

Author: jtriley-eth
Date: 2023-05-10T15:43:00.300Z

Makes sense (re: URI breakout). What would this be called? ERC6909MetadataURI?

Author: RenanSouza2
Date: 2023-05-17T13:34:59.780Z

Hey, the next EIP Editing Office Hour Meeting is being discussed here: EIP Editing Office Hour Meeting 18 · Issue #234 · ethereum-cat-herders/EIPIP · GitHub
would you like to take your proposal there?

Author: jtriley-eth
Date: 2023-05-18T01:18:53.994Z

That would be great! What’s the best way to join the list?

Author: RenanSouza2
Date: 2023-05-18T02:24:50.213Z

it is very simple, just comment in the link above that you want to include your EIP for dicussion at the next meeting and say the EIP number and the PR number

Author: RenanSouza2
Date: 2023-06-05T18:52:26.429Z

I just found this new initiaitve and may be helpful to your project [Current] Agenda for 3rd AllERCDevs (Asian / US Friendly) Tue, 2023-06-13 23:00 UTC · Issue #4 · ercref/AllERCDevs · GitHub

Author: calvbore
Date: 2023-06-12T22:55:58.397Z

One thing that I like about 1155 that isn’t here is the bytes calldata _data as additional arbitrary data in an unspecified format in the transfer functions. Personally, I would like to see them here as well, as they could be useful for extensions to the standard or for a specific token implementation.

Author: RenanSouza2
Date: 2023-06-14T14:14:21.765Z

I can’t talk for @jtriley-eth but AFAIK the data was used to call the receiver, this is no longer required and you could still make a function that accepts data

Author: jtriley-eth
Date: 2023-06-21T18:52:43.925Z

As @RenanSouza2 mentioned, arbitrary data is useful for callbacks, but the core ERC doesn’t include callbacks, so there wouldn’t be much use for it that I’m aware of.
Though a callback extension may be useful in the future.

Author: frangio
Date: 2023-08-28T19:40:26.080Z

Just catching up with this ERC and I have a few questions.


What’s the reason for the boolean return values?


One minor issue with ERC-20 is that the Approval event is not sufficient to compute allowances because transferFrom decreases allowance without any identifying events (Transfer doesn’t log the spender). This ERC has the exact same behavior. OpenZeppelin Contracts used to emit Approval during transferFrom to mitigate this, but we are removing it as it’s non-standard and the additional event adds significant gas cost. It would be nice to think this through for this ERC and have some kind of answer. Some thoughts:

What is even the point of the Approval event if it can’t be used reliably? The only answer I can come up with is that it allows implementing things like https://revoke.cash, a very important application so it makes sense to keep some event for approve. That said, in practice you only use it to check that an approval happened at some point, and whether there is any allowance remaining needs to be recovered via the allowance getter. So perhaps the Approval event should not contain a parameter for amount, given that it’s not reliable?
An alternative is to include the spender/operator in the transfer events, which then allows to compute allowances based on the events. ERC-1155 TransferSingle and TransferBatch include an operator parameter. Adding a parameter it would have to be non-indexed, and it would only cost 250 additional gas.



Why isn’t the spec stricter about not decrementing allowance when the spender is an operator?


Why isn’t the spec stricter about emitting Transfer for mints and burns?


The motivation for the ERC-1155 callbacks and the “safe” nomenclature come from the fact that they protect against lost tokens by checking that the receiver is able to handle them. Since this protection is removed, now wallets are responsible for preventing that kind of error. I think the ERC should document the risk and recommend possible mitigations (e.g., wallets should consider rejecting transfers where the receiver is set to the token itself).

Author: Philogy
Date: 2023-08-29T19:52:12.703Z

Question regarding the standard, why require the transfer, transferFrom, approve and setOperator functions to have a constant return boolean of true? Explicitly returning a value even a constant costs gas.
The only reason I could potentially see is backwards compatibility, however the functions have different inputs and therefore selectors anyway so they wouldn’t really be backwards compatible anyway?

Author: stoicdev0
Date: 2023-09-07T13:16:07.374Z

Looks really clean! Some thoughts:

How about using mediaURI instead of image for the metadata? So it doesn’t suggest it is limited to images.
While I agree the word “safe” is misleading,  the callback is big deal for advanced implementations. Can you reconsider adding a way to notify the destination contract that a token is being transferred to it? Maybe something along “transferFromAndNotify”?
A small size optimization on reference implementation: transfer can simply call transferFrom instead of repeating the logic.  Moreover, do we even need an transfer method?

Author: jtriley-eth
Date: 2023-09-07T14:41:37.544Z

Why Boolean Return Values
When a contract is called, if no data is returned, Solidity and Vyper perform extcode checks to assert the called address is a contract account and not an externally owned account. Reading a boolean from returndata is cheaper than external code checks.
Approval Issue
The Approval event is still useful for a few reasons.

It indicates an approval at some point in an account’s history, which is useful
As a convention, events to log state mutations is good practice.
The information can still be gathered in a few different ways.

Getting a spender+owner allowance using strictly events is still possible with one additional event filter (Transfer with matching spender and owner). But the more efficient way to aggregate allowances would be to call the ‘allowance’ getter once per account on the first approval event, negating the need to aggregate events, regardless of whether all delegated token movements log a new Allowance event.
Strictness on Operator Allowances
On operator allowances, the convention ‘should’ follow the specification’s recommendation, but there may be edge cases where an implementor may want more flexibility in allowance updates for operators.
Strictness on Mint and Burn Events
Since mint and burn functions are not included in the specification, it seems out of scope to definitively constrain the behavior of the functions. The ‘should’ notation indicates that this is convention and would be good practice, but overall the ERC aims to remove all constraints that aren’t strictly necessary.
Safe Transfer Notes
It seems redundant to document this behavior, as any transfer to an unintended address would almost always result in loss of funds, regardless of the kind of account receiving the token.

Author: jtriley-eth
Date: 2023-09-07T14:51:03.875Z

Thank you!
Media URI
This makes sense, would this cause more issues with integrating the metadata into client libraries? I would assume if all other token metadata standards contain the ‘image’ tag it would be more of a hurdle for integrations.
Callbacks
Callbacks in theory seem useful, however I’m not aware of ERC-1155 callback usage in practice. Protocols with callback systems seem to implement their own callback system beyond the standard anyway. I think it would be best to leave callback systems unconstrained and allow implementors to decide on their callback architecture.
Transfer vs TransferFrom
The reference implementation maximizes for readability over size or runtime gas optimization. On removing transfer altogether, I would personally prefer keeping both to remove unnecessary checks and branches on simple transfers, though I’m curious to hear other opinions on this one.

Author: frangio
Date: 2023-09-07T19:22:00.843Z

Should contracts that interact with ERC-6909 tokens validate that function calls return true or should the return value be ignored?



 jtriley-eth:

events to log state mutations is good practice


I agree with this, what I’m pointing out is that not all state mutations are logged in the current spec.



 jtriley-eth:

Getting a spender+owner allowance using strictly events is still possible with one additional event filter (Transfer with matching spender and owner).


I don’t think this is true. The spender is not logged in the Transfer event. Would you be open to adding it?



 jtriley-eth:

the more efficient way to aggregate allowances would be to call the ‘allowance’ getter once per account on the first approval event


This doesn’t get you the current up to date allowance, only the initial one, am I understanding correctly?

Author: jtriley-eth
Date: 2023-09-07T19:30:06.008Z

Callers to ERC6909 should validate return values in my opinion, but that’s beyond the scope of the specification.
All state mutations are logged, as the allowance delta is implied by the transferFrom call.
You’re right, it’s not logged in the Transfer event, my mistake, though I’m hesitant to break the Transfer event convention for this case. Most indexing software can account for getting the caller of the transferFrom method.
No, what I mean is you can use Approval to see spender/owner pairs that have had a non-zero approval in the past, but an allowance call will return the up-to-date allowance for the spender and owner. This can also be batched efficiently in a few different ways.

Author: frangio
Date: 2023-09-07T19:52:23.573Z

jtriley-eth:

Callers to ERC6909 should validate return values in my opinion, but that’s beyond the scope of the specification.


So it would be a valid use of an ERC-6909 token to make a transfer and ignore the return value. I guess this makes sense given that the spec says implementations “MUST return true”, any other behavior is non-compliant so anything could happen. I think the most likely outcome is that users will not validate the return values.



 jtriley-eth:

Most indexing software can account for getting the caller of the transferFrom method.


As far as I know this requires traces, so it definitely places higher requirements on indexers. I’m only familiar with Dune and Allium, and the caller is not available in their logs tables.
This ERC is already deviating from existing Transfer events, so I don’t think there’s a strong convention that would be broken by adding the spender. Compared with ERC-20, it adds an id. Compared with ERC-721, it adds an amount. Compared with ERC-1155, it removes the operator/spender.
My vote would be for adding the operator in the event. If not, I would remove the amount from the Approval event, to make it explicit that it should not be relied upon, and that checking allowance is necessary.

Author: stoicdev0
Date: 2023-09-13T15:40:40.648Z

jtriley-eth:

Media URI
This makes sense, would this cause more issues with integrating the metadata into client libraries? I would assume if all other token metadata standards contain the ‘image’ tag it would be more of a hurdle for integrations.


You are creating a new standard which is not backwards compatible, why carry the limitations of previous ones?



 jtriley-eth:

Callbacks in theory seem useful, however I’m not aware of ERC-1155 callback usage in practice.


I’m sure I’m not the only one who wishes ERC20 had callbacks, it would make several use cases much simpler. It is the same case for NFTs, having the option to notify a destination contract about an NFT being sent prevents you from having 2 do to calls, 1 to approve the destination contract to manage your NFT, and another for the destination contract to pull it.

Author: jtriley-eth
Date: 2023-09-24T13:08:13.270Z

After thinking on this and discussing this with a few early implementors, I think a ‘caller’ or ‘operator’ as the first indexed argument makes sense. The updated event will have the following schema.
- name: Transfer
  type: event

  inputs:
    - name: caller
      indexed: true
      type: address
    - name: sender
      indexed: true
      type: address
    - name: receiver
      indexed: true
      type: address
    - name: id
      indexed: false
      type: uint256
    - name: amount
      indexed: false
      type: uint256

In Solidity:
event Transfer(
    address indexed caller,
    address indexed sender,
    address indexed receiver,
    uint256 id,
    uint256 amount
);

Author: frangio
Date: 2023-09-24T16:36:03.444Z

That’s great!
One point I might disagree with is making the caller indexed and not the token id. In practice I’m not sure indexed arguments make a big difference, but if I had to choose an access pattern to prioritize it would be querying by token id. What do you think?

Author: frangio
Date: 2023-09-29T21:40:35.470Z

If Solmate is already shipping an implementation, this ERC should start moving towards finalization.
I like this ERC because it’s minimal, but I wish it also provided a good answer to the UX and security issues with approvals and operators. By UX I’m referring to having to send 2 transactions in order to use a token with an app, and by security I’m referring to the issues that arise from using infinite approvals or operators in order to mitigate this UX annoyance.
Lately I’ve come to think that temporaryApproveAndCall might be the best pattern to solve that issue: a function to set an allowance, make a call to a target contract with specified data (wrapped in a handler function like onTemporaryApprove), and reset the allowance afterwards. In a world with transient storage, this can also be very efficient. For ERC-6909 in particular I’m not sure how this pattern should be combined with multiple token ids, perhaps there would be a variant for temporary allowance of a single id, and a variant for a temporary operator for all ids.
Curious to hear thoughts about the issues and this proposed solution in particular.
My concern is that with ERC20 we’ve seen many many attempts at standardizing a solution to this problem and none has gotten wide adoption[^1] because they were separate standards and came too late. I would hate to see something similar happen around ERC-6909 when there is the opportunity to address it from the start. The subsequent popular token standards ERC721 and ERC1155 both had a solution in the form of “safeTransfer”, which I think shows that there is demand for a solution.
[^1]: Except perhaps permit, but this is not even a full solution to the UX problem because it still requires two wallet interactions (signature + transaction).

Author: jtriley-eth
Date: 2023-09-30T01:58:03.608Z

I agree, we should finalize sooner rather than later.

On transfer delegation, I’m not sure a minimalist multi-token standard is the right place for experimentation on this front. There is demand for alternate solutions, I do agree with this, but there doesn’t appear to be a clear winner in terms of how to implement such a scheme.
Operators cover the delegate-everything case, while approvals cover the granular conservative case; each without external interaction or complex operations.
Callback systems are great on the protocol-level where protocols are able to create their own interfaces and functionality more tailored to their use case. Signature permit systems are also useful and vary from protocol to protocol, but I think these are things that should be constrained by protocols, not the core standard.

Author: frangio
Date: 2023-09-30T16:10:10.771Z

If the only goal of this ERC is to be a minimalist multi-token standard, I agree.
I guess my point was that I’d like to see this ERC pursue the more ambitious goal to supercede all 3 currently popular token standards and fix their flaws. I think this ERC could be that but is missing a solution to UX and security.
I bring it up in this discussion, instead of leaving it as a protocol-level decision, to consider applications that have to work with arbitrary tokens. For example, I’d like an ERC-6909 exchange to be able to rely on the existence of this solution (be it temporaryApproveAndCall or something else). If it’s not standardized and not widely deployed (as it would be if it was part of the token ERC), then the applications won’t use it.
In any case I will experiment with some designs, and perhaps that can become a separate ERC. I do think it makes sense to decouple this solution from the core of the token, but it will definitely not be adopted as widely.

Author: wminshew
Date: 2023-09-30T20:57:51.977Z

Looks great, looking forward to it being finalized

Author: wwhchung
Date: 2023-10-13T21:36:51.153Z

Why is amount indexed in the transfer event?

Author: HayattiQ
Date: 2023-10-21T08:26:48.233Z

Very good. I hope many Marketplace support this ERC token

Author: RenanSouza2
Date: 2023-10-26T18:21:19.598Z

I have one suggestion for the ERC after implementing my own version of the code
the term ‘sender’ is easily mistakable with ‘spender’ phonetically, what it makes s bit more confusing is the fact that usually msg.sender becomes the spender not the sender
My suggestion is to intead of using ‘sender’ using the term ‘from’ or ‘origin’ so they don’t clash this much

Author: RenanSouza2
Date: 2023-10-27T12:08:49.495Z

my seccond suggestion is to use the term operato to whoever is triggers the execution and allowed operator for the setOperator and isOperator functions
so the functions would become
allowance(owner, operator)
isAllowedOperator(owner, operator)
What are yout toughts @jtriley-eth ?

Author: jtriley-eth
Date: 2023-10-27T15:48:28.363Z

Re first suggestion: While variable names are technically constrained by the standard in the yaml spec, it’s functionally equivalent, so either would be acceptable.
Re second suggestion: Same as above for the variable names, contracts don’t account for variable names, only function names and argument types. Though I prefer isOperator over isAllowedOperator for simplicity. I think they both convey the same message given the usage of ‘operators’ in the past.

Author: frangio
Date: 2023-11-21T22:31:21.610Z

frangio:

I like this ERC because it’s minimal, but I wish it also provided a good answer to the UX and security issues with approvals and operators. By UX I’m referring to having to send 2 transactions in order to use a token with an app, and by security I’m referring to the issues that arise from using infinite approvals or operators in order to mitigate this UX annoyance.


I’ve put together a first draft of a proposal: ERC-6909 Usability & Security Extensions. As discussed before this is not intended for ERC-6909 itself, but it could become its own ERC if others think something like this is a good idea and necessary. Feedback is welcome!

Author: neodaoist
Date: 2023-11-26T01:03:49.862Z

Great proposal. I’ve been experimenting the past month+ using ERC6909 for a few multi-token ideas and I love it. It’s clean, consistent, expressive, and flexible. It’s the multi-token standard of the future.
Metadata Extension Feedback
The one point of inconsistency is in the Metadata Extension — static name(), static symbol(), but dynamic decimals(uint256) does not make sense imo. I struggle to find a use case that would benefit from dynamic decimal values based on token ID, but not also from dynamic names/symbols.
NFTs
For the case of ERC721-style NFTs, decimals always equal 1, and there is a single, unifying name and symbol for the collection. So, doesn’t benefit afaict from dynamic anything here.
Token Wrappers
For an ERC20 token wrapper, which I believe was the initial inspiration for supporting dynamic decimals values per token ID, it will also be useful to have dynamic names/symbols. Let’s take as an example, a single 6909 contract wrapping USDC, DAI, FRAX, etc — the former could return 6 decimals, the latter two 18.
But how is a user or integrator to disambiguate between which ERC20 is being wrapped by which token ID? That is a perfect use case for name/symbol — “xyzUSDC”, “xyzDAI”, “xyzFRAX”, etc. Otherwise, for this use case, we would be leaving it up individual implementors to decide how to map token ID to an explanatory name or other indicator of what token is being wrapped (perhaps the underlying ERC20 contract address). Which then puts pressure on downstream integrators and marketplaces to support custom implementations to display this info to end users.
DeFi Derivatives
For a similar use case of a derivative or some kind of synthetic asset, developers may be more likely to want to define a consistent scalar unit and return that from decimals(), but give an expressive name/symbol to each token ID. For example, take a tokenized options protocol — decimals() might always return 18 but depending on the underlying asset(s) and instrument type of a given token ID, both name and symbol might return “xyz-ETH-USDC-1DEC23-2100-C”.
It’s important that name(id), symbols(id), and decimals(id) are all dynamic based on token ID, for any use case that looks like, “combine multiple ERC20 contract deploys into a single, unified multi-token contract.”
Again, without this, we rely on each implementor to disambiguate the semantic meaning of various token IDs in their own way, which fragments the common usage of ERC6909 and creates headaches for downstream integrators and marketplaces.
DeFi LP Tokens
This is another use case where it is more likely for a developer to be okay with static decimals but want dynamic names/symbols. Think of Uniswap v4’s planned ERC1155 balance accounting (or perhaps ERC6909 =), or any singleton AMM design — the name/symbol can specify what the LP token is.
Gaming
For gaming, the original motivation behind ERC1155, the content of in-game items may be best suited to definition in URI JSON. And potentially this is a use case where a developer would want a single unifying name and symbol for their collection, and dynamic decimals values (eg, in-game collectible items vs. in-game currency). Although still I’d argue that varying decimal values between token IDs makes them conceptually and fundamentally different enough, such that having a way to disambiguate them that is more onchain-native than having to parse JSON is important for developer ergonomics.
Aside on Syntax
Semantics aside, there is one syntactic wrinkle yet in both the current Metadata Extension and my proposal, but I don’t think it’s a big deal —
Because decimals is already plural, when the function is decimals(id), we pedantically might be tempted to call it whatever the plural of decimals is (?), or something like decimalss, decimalz, etc =). But in practice, imo it’s idiomatic enough that developers can understand the expectations and semantics.
tl;dr
Excellent minimal multi-token spec. But for consistency and flexibility across sectors/use cases, the Metadata Extension names/symbols/decimals should either be (1) all dynamic based on token ID, or (2) all static and not take token ID as a parameter. Imo the former is significantly more powerful and more likely to provide great DevEx for future developers and integrators, while still keeping with the ‘minimalist’ spirit of this EIP.
Proposed Metadata Extension Interface
(And of course, these could be implemented as some combination of private state variables and public/external functions, which contain some business logic or just return constant values, such that not all returned values need come directly from storage.)
interface IERC6909Metadata is IERC6909 {
    function names(uint256 id) external view returns (string memory);
    function symbols(uint256 id) external view returns (string memory);
    function decimals(uint256 id) external view returns (uint8);
}

Author: jtriley-eth
Date: 2023-11-26T11:02:01.174Z

so expanding on this, 6909 is trying to accommodate two very different use cases, namely singleton defi architectures and nft collections. both have different needs in terms of metadata, ie:
a defi protocol that wraps external tokens will need token-level metadata: name, symbol, and decimals
an nft collection where all token ids are a part of the same collection would need collection-level metadata: name and symbol
what are thoughts on breaking these out into two separate interfaces?
// nft collection-level metadata
interface IERC6909CollectionMetadata is IERC6909 {
    function name() external pure returns (string memory);
    function symbol() external pure returns (string memory);
}

// defi token-level metadata
interface IERC6909TokenMetadata is IERC6909 {
    function name(uint256) external view returns (string memory);
    function symbol(uint256) external view returns (string memory);
    function decimals(uint256) external view returns (uint8);
}

Author: neodaoist
Date: 2023-11-26T19:38:54.002Z

I like this approach. It should work well for both high-level use cases (NFT collection and DeFi singleton), while keeping the core of the spec consistent. Had the same idea as a morning shower thought =)
And how about the inheritance graph for MetadataURI? With this approach of dedicated interfaces for collection metadata and token metadata, imo in the reference implementation we can treat MetadataURI as a standalone interface/contract.
EDIT — because I could see either use case potentially needing tokenURI(id)

Author: frangio
Date: 2023-11-26T19:50:51.149Z

Not a fan of the approach because it fragments the 6909 ecosystem into two incompatible interfaces. I would favor metadata methods with a tokenId parameter, where the token is free to return the same value for all ids.
Note that for collection-level metadata the standard is lacking in another way: see OpenSea’s Contract-level metadata documentation, which proposes a contractURI() function where 6909 only has tokenURI(tokenId). I think this is potentially more important than collection-level name() and symbol().

Author: neodaoist
Date: 2023-11-26T20:08:43.599Z

It’s a good point re contractURI(). Included it below.
Setting aside inheritance, and whether it’s a good idea to have multiple extensions based on high-level use case, below is the metadata and URI functionality we’re discussing, plus a point about total supply.
Any thought on how we might bring back totalSupply(id) as optional? Gas optimization is important of course, but aren’t we concerned that many of the “combined ERC20” or “combined ERC4626” use cases absolutely need a standard totalSupply(id) function?
I bet we can find a way to rationalize all of this, while keeping the spec clean + minimal + flexible.
EDIT — to be clear, I’m not proposing this many discrete extensions, just laying it all out
Metadata
// collection-level metadata
interface IERC6909CollectionMetadata {
    function name() external pure returns (string memory);
    function symbol() external pure returns (string memory);
}

// token-level metadata
interface IERC6909TokenMetadata {
    function name(uint256) external view returns (string memory);
    function symbol(uint256) external view returns (string memory);
    function decimals(uint256) external view returns (uint8);
}

Content URI
// collection-level URI
interface IERC6909CollectionURI {
    function contractURI() external view returns (string memory);
}

// token-level URI
interface IERC6909TokenURI {
    function tokenURI(uint256) external view returns (string memory);
}

Supply
// collection-level supply
interface IERC6909CollectionSupply {
    function totalSupply() external view returns (uint256);
}

// token-level supply
interface IERC6909TokenSupply {
    function totalSupply(uint256) external view returns (uint256);
}

Author: neodaoist
Date: 2023-11-28T16:38:45.084Z

Summarizing from discussion @jtriley-eth and I had over the past couple of days.
Here’s where we netted out:
// metadata (can be either collection- or token-level)
interface IERC6909Metadata {
    function name(uint256) external view returns (string memory);
    function symbol(uint256) external view returns (string memory);
    function decimals(uint256) external view returns (uint8);
}

// content uri (covers both collection- and token-level)
interface IERC6909ContentURI {
    function contractURI() external view returns (string memory);
    function tokenURI(uint256) external view returns (string memory);
}

// token-level supply
interface IERC6909TokenSupply {
    function totalSupply(uint256) external view returns (uint256);
}

Thought Process

Taking into account @frangio feedback re interface fragmentation, there is a single Metadata interface — and a dev could choose to implement the IERC6909Metadata functions w/ unnamed parameters, always returning the same value for name/symbol/decimals
We considered splitting contentURI and tokenURI(uint256) into separate interfaces, but is there really a use case where a dev wants rich media to show up for a token/NFT, but Not show up on their OpenSea or other marketplace collection page? Does not seem likely
Rename MetadataURI to ContentURI — (1) this is typically more the ‘content/data’ vs. the ‘metadata’ in practice (for NFT collections especially), and (2) this naming enables us to minimize inheritance, so implementors can pick from one or more of these extension interfaces, a la carte
Remove ‘Token’ from Metadata and ContentURI extensions, bc there’s nothing to disambiguate, but keep ‘Token’ in Supply, bc that is specifically for use cases needing to specify total supply at token-level
We could do IERC6909CollectionSupply.totalSupply() as a fourth interface, for onchain use case of ‘how many NFTs in this collection are there’, but does not seem critical

Author: frangio
Date: 2023-11-28T20:21:05.867Z

neodaoist:

We could do IERC6909CollectionSupply.totalSupply() as a fourth interface, for onchain use case of ‘how many NFTs in this collection are there’, but does not seem critical


I agree that on-chain supply for non-fungible tokens doesn’t seem important like it does for fungible tokens. For the record, this has the issue that Etherscan currently doesn’t display supply of e.g. ERC721 tokens unless they implement a totalSupply() function, but Idon’t see a reason why they couldn’t fix that. I see that the Transfer events for minting and burning are now specified as “MUST” which is great in that sense.

Author: RenanSouza2
Date: 2023-12-14T17:27:25.635Z

hey @jtriley-eth
are you discussing this erc in some discord or timgs like that?

Author: wminshew
Date: 2023-12-15T21:18:27.344Z

@jtriley-eth any idea when this might be finalized? have a project coming up I’d like to use it for
also where’s the best place to view the latest draft – I don’t suppose it’s this?

Author: jtriley-eth
Date: 2023-12-29T15:51:22.734Z

have received messages and conversations across a few platforms, hard to get every convo into the same forum

Author: jtriley-eth
Date: 2023-12-29T15:55:11.226Z

the eip website is up to date now.
re finalization: there’s an open pr to move from ‘draft’ to ‘review’ at the time of writing. will move to finalization as soon as possible. though if you need it soon, the current iteration of the core is the final iteration, so there should be no breaking issues here.

Author: RenanSouza2
Date: 2023-12-31T23:00:47.158Z

I’m asking because I don’t want to reopen discussions that probably have been done before
My current questions are:

Why the totalSupply have been moved to an extension? maybe this can be expanded in the rationale
Can the decimal function revert? is there an default behaviour for an ID not yet used?

Author: jtriley-eth
Date: 2024-01-01T15:20:51.661Z

re totalSupply: this is also non-existent in the erc1155 standard. the totalSupply adds an extra disk write on mint and burn, which affects nft mints and defi protocols where wrapping/minting and unwrapping/burning is a common occurrence. totalSupply has a few onchain use cases, so moving it to an extension sacrifices some composability with protocols that need the totalSupply in favor of significantly cheaper gas costs.
re decimals: i hadn’t thought of this; so at the time of writing, tokenURI may revert if the tokenId doesn’t exist, but this is likely called offchain almost exclusively, whereas decimals may be used onchain as well. while these functions are different, i think they should follow the same rule for consistency. i’m open to suggestions here but my initial thought is:

decimals must not revert if tokenId does not exist
decimals should return 0 if tokenId does not exist
tokenURI must not revert if tokenId does not exist
tokenURI should return an empty string if tokenId does not exist

the rationale for “should” in regards to returning a default is there may be cases where the decimal amount is hard coded, but in general it should fall back to the default value.
// happy case
mapping(uint256 id => uint8) public decimals;

// edge case
function decimals(uint256) external pure returns (uint8) {
    return 18;
}

Author: RenanSouza2
Date: 2024-01-01T15:39:36.203Z

Makes sense and I actually really like that
I think he totalSupply part is important to be in the retionalle
Can you point mr the discord you are using if possible?

Author: jtriley-eth
Date: 2024-01-01T16:40:17.097Z

sounds good, will update the totalSupply bit in rationale
and discord server is here (channel #6909): Ripped Jesus Gang

Author: frangio
Date: 2024-01-01T16:43:48.727Z

The notion of “tokenId does not exist” is ambiguous, so it should be avoided or defined. Does it mean the token has no supply? That it has never been minted?
I agree with must not revert, but I’d remove the “should” items, I’m not sure they add value.

Author: jtriley-eth
Date: 2024-01-01T16:56:42.166Z

if the wording is kept, then definition would be “no supply”
re rm “should”: do you think the statement should be replaced with something else like “must” or just remove it altogether?

Author: frangio
Date: 2024-01-01T17:29:58.778Z

I’d remove them altogether (talking about “should return 0/empty string”).

Author: frangio
Date: 2024-01-27T23:21:03.751Z

I’m preparing an ERC draft for the extension I shared above (ERC-6909 Usability & Security Extensions).
I have a question about compliance: if a token does a temporary approval or sets a temporary operator that resets at the end of the function call, is it necessary to emit the Approval or OperatorSet events?
Argument for emitting: an indexer might break if it sees a “transferFrom event” without the corresponding allowance or operator status. Argument for not emitting: significant gas savings (at least one fewer event, possibly two), and there is no need to track the action in an indexer anyway because the value is set transiently.
I would prefer not to emit the event(s) but the ERC states:

Approval MUST be logged when the allowance is set by an owner.
OperatorSet MUST be logged when the operator status is set.

The fact that the values are set transiently feels like a significant change that may justify a lack of events.
CC: @jtriley-eth
Edit: I am realizing the ERC is still technically in Draft status. It could be interesting to encode these relaxed rules in the ERC if the authors are open to it. Namely, that the events may be omitted if the values are only set transiently.

Author: jtriley-eth
Date: 2024-01-29T02:25:23.236Z

good catch. i think it could use a little rewording
thinking we make allowance and isOperator storage variables explicit in event log conditions, so since transient approvals don’t mutate these storage variables, there’s no need to log the event
defs leaning into the ‘no transient logs’ camp since the transient variables being dropped at the end of the transaction would also require an event
though mention of transient variables in the spec may be useful as well

Author: frangio
Date: 2024-01-30T19:37:06.086Z

jtriley-eth:

thinking we make allowance and isOperator storage variables explicit in event log conditions


The spec should try to be implementation agnostic, so I wouldn’t do it this way.
That said, after thinking about this a bit more I realized that omitting these events may mess up allowance indexers, which we previously said were a Good Thing. So I would make the event optional but recommended in these cases, so it’s up to the implementer to take the risk or not.
Perhaps something like this…?

OperatorSet
MUST be logged in a transaction that results in a change of operator status.
SHOULD be logged for transient changes within a transaction. If omitted, offchain indexers may underestimate allowance.


Approval
MUST be logged in a transaction that results in a change of allowance, unless it was caused by transferFrom.
SHOULD be logged for transient changes within a transaction. If omitted, offchain indexers may underestimate allowance.

Author: zachobront
Date: 2024-02-20T21:10:10.454Z

@jtriley-eth Small nit but in the Metadata Extension you have:

name
The name of the contract.


symbol
The ticker symbol of the contract.

Given that there’s an id input argument, I assume these should be the name and symbol of the given token, not the contract.

Author: beringela
Date: 2024-05-16T09:02:22.769Z

In the section on “backwards compatibility” it says a wrapper can be implemented for ERC-20.
Is my understanding correct: the idea here is that the ERC-6909 contract would have the ERC-20 methods added, so that it appeared to be a normal ERC-20. However, the methods would all be interacting with just ONE of the collection of ERC-20s that live inside the ERC-6909.

Author: ashhanai
Date: 2024-07-31T07:10:57.187Z

Great ERC draft. Few comments.

It could be clearer if the token must implement ERC-165. If the intention is that the token must implement it, please add it explicitly into the proposal. If not, I would argue that it should be included as it’s the most convenient way how to discover tokens transfer functions. ERC20 doesn’t have it, and it’s a big pain for protocols working with several token standards at the same time.
I would vote for removing the transfer function and keeping only transferFrom. Any protocol where transfer logic is independent of transfer implementation (e.g., transfer this asset to this address, not knowing who the operator is) will have to perform the check either way, wasting gas savings from keeping both functions.

Author: jtriley-eth
Date: 2024-10-20T02:14:00.869Z

appreciate your patience on this, quick update:

moved to review
name/symbol method description updated (per @zachobront)
erc-165 requirement is now explicit (per @ashhanai)

Author: jtriley-eth
Date: 2024-10-20T02:16:08.121Z

i’d prefer the transfer & transferFrom, following erc20’s lead & for off-chain interactions for simple transfers, as it’s significantly more expensive to do the latter as an eoa

Author: arr00
Date: 2024-12-03T18:10:48.968Z

Small nit on the spec for allowance:
allowance
The total amount of a token id that a spender is permitted to transfer on behalf of an owner.
Based on the current spec, it seems that if one calls allowance(owner,spender,id) where spender is an operator for owner, the return value should be type(uint256).max. Based on the reference implementation that is not the case. Should this be clarified?

Author: z0r0z
Date: 2025-04-10T14:59:53.593Z

I have deployed an educational, though hopefully also useful example of ERC6909 singleton, Coins.sol, to this address on Ethereum and Base, 0x0000000000009710cd229bf635c4500029651ee8. It is backwards compatible with ERC20. Each new ID creation also creates “twin” ERC20 clone with create2, such that the ERC20 address matches the ERC6909 ID. Calls to the ERC20 also update the ERC6909 state, and vice versa. Also to @arr00 recommendation, the ERC20 allowance method returns max uint256 if an operator is set (though I did not include this in ERC6909 side, as want to follow existing standard recommendation).