FA2.1 / FA3 - It's time

I think it’s time we have an open thread, collecting everything from developers in terms of needs and gripes regarding the current FA2 standard to devise a FA2.1 or FA3 token standard for Tezos because:

  • Views open up a lot of possibilities
  • There are many platforms that have need for royalties
  • We need to expand on allowances enabling flash-loans, subscription models
  • How can we best standardise integration with tickets?
  • State-channel / ZK-rollups / Optimistic Rollups - anything needed to be taken into account
  • Upgradeability / Forking / Upgrade paths
  • Global Table Of Constants - let’s find a way to maximize the space-saving enabled by this

Thoughts, ideas, requests are all welcome

18 Likes

Might also be a an occasion to:

  1. Integrate tzip-17 into it (and fix it, tzip-17 is currently not very useful because it requires all transactions to lock on a global counter for the contract).

  2. Clean up this all operator affair. FWIW I still don’t understand it.

  3. Maybe reintroduce finite allowances? Ctez uses FA1.2 because it needs a type of functionality that requires finite allowances (or maybe it could use operators, but I don’t know because I still don’t understand them :man_shrugging:).

  4. Standardize use of tickets attached to an FA contract.

10 Likes

Hi ,

Can Non-Transferable Token functionality be added to the new standard as it is not supported by the current FA2 standard natively?

4 Likes

Should the privacy features also require integration in the standard ? (I am no dev, could be a non relevant comment)

Supply metrics are also a mess in current tokens and thats because here is no spec for it so every contract does it differently. This should be standardized imo

1 Like

Maybe a Token Governance standard for Upgradeability / Forking / Upgrade paths…

1 Like

It is time to add a transfer_and_call mecanism.

It would simplify things by a lot, open new possibilities and remove security problems encountered with operators.

I agree with @murbard that we should consider moving back to finite allowance or even remove allowance mechanism thanks to transfer_and_call.

On January 7, I made a proposal about using tickets as bank check for FA tokens.

The idea is that you can emit a ticket that can be cashed anytime on the target FA contract.

It differs from the idea of tickets representing the token. It’s only a right to cash it.

A typical transaction scenario is:

  1. Alice crafts one fa3 check with fa3_dest dest = KT1…, amount = 20, to = B. token_id = 0.

  2. Alice sends the tickets to contract B.

  3. Contract B. performs the corresponding logic

  4. Contract B. transfers the ticket to the corresponding token contract (KT1…)

  5. Token contract validates the transfer

This would add one entry point: transfer_tickets.

transfer_tickets

(list %transfer_tickets
  (ticket
    (pair
      (string tag)
      (address %fa3_target)
      (list %txs
        (pair
          (nat %amount)
          (address %to_)
          (nat %token_id)
        )
      )
    )
  )
)

tag is always equals to fa3_check.

Each transfer in the batch is specified between one source: the ticket’s forger and a list of destination.

Each fa3_target specifies the address of the FA3 and the same info as in the regular transfer.

This adds a new possible error: FA3_WRONG_TARGET if the %fa3_target is not the same as the current FA3 address.

The %qty of tickets simply multiply the amount transferred.

Like in standard bank check we could add a %timeout LEVEL that indicates when the check cannot be cashed anymore.

1 Like

What are the security problems with operators and how does this mechanism mitigate them? I don’t have historical context on this problem and couldn’t find any discussion in the forum. Thanks!

It would be great to have something similar to EIP-2981.

1 Like

Yeah that would be great, if so it would be nice if it has support for multiple royalty receivers, ie, split royalties.

2 Likes

We need a token standard for token issuance on sidechains and layer2 protocols that are to be introduced.

Since most of the derivatives contracts have maturity periods, it would be great if we have a token standard for tokens with an expiry. ( So the burn will happen automatically on the expiry date/block thus preventing the accidental burning of tokens)

Wrangling metadata on-chain can be awkward with present standards and Michelson language features. Many NFT projects default to putting metadata on IPFS - makes sense if the NFT is signing a large JPEG that you’re storing on IPFS anyway, but makes less sense for things that can live on-chain like procedurally generated works (cf artblocks.io on Eth) - where introducing an IPFS dependency is undesirable.

One thing that I have struggled with building my contract, for example, is how to assemble JSON on chain. Currently I am basically preparing fragments of JSON off-chain and assembled a JSON string in the view from fragments with roll-your-own nat to string / address to string code.

This part of an NFT standard needs to be fully generatable in a view IMO - that means improved language support from Michelson for building JSON strings (see my post in Research and Development), or a move away from (nested) JSON structures as the metadata format.

6 Likes

Hi, there also needs to be a clear way to identify the kind of Token the contract is dealing with (Fungible, Not fungible, Hybrid, …).

We have had the issue in a recent project where an exchange contract needs to decide on which (fungible) side of the offer fees are taken. Since now everything is FA2 it is hard to decide.

It could be solved with a kind of “magic number” a bit similar to IEP165

2 Likes

I have two suggestions:

1 - token standards on Tezos should be approached in a more modular fashion. FA is a beast right now, and if each iteration leads to more features, then FA4 or FA5 will be massive and impossible to maintain. Rather, these ideas should be built out in a modular fashion. For example: let’s say royalties is important, that should be a single focus, and it should not require a whole new FA version, but instead a set of functions that can be optionally attached to the base FA standard if the developer chooses. This is something I’ve been enjoying about ETH/Solidity.

2 - I feel royalties specifically are important, as Tezos has been a chain that is primarily NFT and art/creator centric compared to others that might be more about DeFi/games/etc. So I think it’s worth putting energy into solving royalties specifically, rather than trying to solve all the problems at once.

To solve royalties in a way that covers a wide range of uses, I would propose something similar to EIP2981, but providing multiple beneficiaries/splits. For example, royalties on each resale could be distributed to 5 different charities, each weighted differently. This should be on-chain, to facilitate on-chain splitting, rather than in metadata/JSON.

An example of how it could look, but in Sol:

And like rognierbenoit suggests, there should be a simple way for contracts to detect whether a token supports this interface.

I think a good first step would be to define a simple example of how this would look in a token, and show how a marketplace (like alternative to HEN Marketplace contract) could take advantage of it, rather than relying on their own royalty stores.

10 Likes

KStasii from Madfish did an interesting blogpost about a new token standard:

There are almost no projects built on the blockchain that can be developed without tokens integration. They are essential entities in the crypto world. Different chains create their own token standards that match their peculiarities the best. The token standard design impacts how quickly the system can grow and how quickly other protocols can be implemented.

This article describes two Tezos token standards, their strengths and weaknesses, and how their limitations can be addressed.

FA1.2

FA1.2 is the oldest token standard that was developed at the dawn of the Tezos ecosystem and proposed in the TZIP-7. It was designed as the closest analog of ERC20, with the smallest adoption to the platform reality.

The standard defines the rules for smart contracts that represent the ledger for single assets and the entrypoints to interact with them.

There are 2 regular methods and 3 view methods mentioned in the TZIP:

(address :spender, nat :value)                %approve
(address :from, (address :to, nat :value))    %transfer
(view (address :owner, address :spender) nat) %getAllowance
(view (address :owner) nat)                   %getBalance
(view unit nat)                               %getTotalSupply

approve allows the spender to spend value tokens. Only the owner can allow someone to spend their tokens, which may not fit in with someone’s business logic.

transfer allows the transfer of assets from one account to another; however, only one transfer can be made per call, which makes multi-transfers expensive.

The view functions are simple methods that, instead of updating something in the contract, create the operation that sends the response to another contract. Only the total supply and the user’s balance and allowance can be requested in the standard way.

So, the standard has many disadvantages:

  • Only single assets are supported;
  • There is no way to implement custom logic for the approval (e.g. when it can only be done by the contract admin);
  • There is no storage specification;
  • View methods are just the methods that return operations (fake views);
  • Batch transfers aren’t supported;
  • here is a lack of infinite approvals;
  • Metadata isn’t specified in the standard.

FA2

FA2, introduced in TZIP-12, was designed to mitigate some of the above issues. The standard is very adaptable and allows the creation of single and multi-assets, and fungible, non-fungible, collectible, or hybrid tokens.

There are 2 regular methods and 1 view method specified in the TZIP:

(list %transfer
  (pair
    (address %from_)
    (list %txs
      (pair
        (address %to_)
        (pair
          (nat %token_id)
          (nat %amount)
        )
      )
    )
  )
)
(list %update_operators
  (or
    (pair %add_operator
      (address %owner)
      (pair
        (address %operator)
        (nat %token_id)
      )
    )
    (pair %remove_operator
      (address %owner)
      (pair
        (address %operator)
        (nat %token_id)
      )
    )
  )
)
(pair %balance_of
  (list %requests
    (pair
      (address %owner)
      (nat %token_id)
    )
  )
  (contract %callback
    (list
      (pair
        (pair %request
          (address %owner)
          (nat %token_id)
        )
        (nat %balance)
      )
    )
  )
)

transfer allows the transfer of assets from one account to another. Batch transfers are supported: a few assets can be transferred from a few senders to many receivers.

update_operators allows the spender to spend any amount of the tokens of some user. Many approvals can be done per transaction.

balance_of is a callback view to receive the balances of some users. The operation can return the balances of many tokens and users.

The standard also describes the token metadata format. The balance update rules are also mentioned but are not obligatory.

Although the standard is more complete than FA1.2, there are still some disadvantages:

  • Only infinite approval is supported;
  • There is no storage specification;
  • View methods are just the methods that return operations (fake views);
  • There are no views for supply metrics.

Protocol Changes

There were a few cool perks introduced in Hangzhou.

Timelock allows the user to send a transaction whose content won’t be seen for some time. Michelson on-chain views makes view-only interactions between contracts easier. The global constants table allows some Micheline expressions to be stored globally and then be found by the hash and used by any contract.

So, it would be nice to reflect these changes in the token standard.

Discussion

As the key solution to approach the protocol changes and dev experience improves, the new token standard can be considered. The main discussion around it takes place on Agora. However, it is not very active.

Long story short, it has been proposed that the standard should have the following features:

  • Supports transfer_and_call mechanics;
  • Supports views;
  • Has the global table of constants;
  • Specifies and has views for supply metrics;
  • Supports finite allowances;
  • Supports tokens with expiries;
  • Supports non-transferable token functionality;
  • Integrates tickets;
  • Implements privacy features;
  • Supports upgradeability mechanics;
  • Supports allowance for flash-loans and subscription models;
  • Is compatible with state-channels, ZK-rollups, and optimistic Rollups;
  • Has optional royalties support;
  • Integrates TZIP-17.

Essentially, an all-in-one token.

I also tried conducting a survey on the dev slack channel and only got 5 eye reactions () and no responses (what engagement, lol). So, I ended up discussing it with the MadFish team.

The main goal was to ascertain whether there are any issues relating to token standards that make development painful and hence, whether there is a need to create a new standard. Finally, we agreed that the main problem is the views that aren’t really views but rather methods with callbacks. We deserve true views integration but developing a new token standard and integrating it into the existing infrastructure seems to be too tricky.

Suggestions

In my opinion, the standard should be as simple as possible and shouldn’t cover everything in the world. It should only specify the payment and assets management logic and be easily extendable by other standards. So, the specification for flash-loans, privacy, tickets, obligatory TZIP-17 integration, royalties support, etc., seems to be redundant in the token standard and should be proposed in separate TZIPs.

I like the idea of creating a new perfect token standard, which would basically be an improved FA2 token with true views and some obligatory cool perks, but I consider this to be unrealistic.

What seems to be realistic is a standard that extends the FA2. This means that new TZIPs can be proposed that can be followed in future FA2 tokens. The tokens that will be implemented according to it will be still compatible with all protocols that support FA2. Let’s look at the additional features.

Compatible with FA2

All the requirements for the FA2 standard must be implemented.

Extended with obligatory views

The views are referring to the views introduced in Hangzhou

The token that supports the new standard must implement the balance_of view with the same signature as the method balance_of . get_total_supply returns the number of tokens currently in circulation. get_max_supply returns Some(max_supply) if the token supply is capped and None otherwise. get_supported_standards returns all the supported standards; it can be used to ensure that the token supports tickets, permits, or the flash loan standard.

Enable transfer and call functionality

Support is the method that allows a user to transfer tokens and then “notify” another contract. The method “implementation” resembles %transfer with the only difference being an extra parameter %callback – the entry point of another contract that will be called.

(pair %transfer_and_call (contract %callback unit)
  (list %transfers
     (pair (address %from_)
           (list %txs (pair (address %to_) (pair (nat %token_id) (nat %amount)))))))

Support finite allowance

The token must implement the %approve method that allows it to give the approval to spend a fixed amount of tokens. The method works for all types of tokens (fungible, NFT, collectibles). The method must not fail if the approved amount is higher than 1 in the case of NFTs.

(list %approve (or (pair %add_operator
     (address %owner)
     (pair (address %operator) (pair (nat %token_id) (nat %amount))))
  (pair %remove_operator
     (address %owner)
     (pair (address %operator) (pair (nat %token_id) (nat %amount))))))

Summary

In summary, I believe that we need a new standard that would extend the TZIP-12 and make it more dev-friendly.

The main features that should be introduced are:

  • Support of views:
    • balance of the user
    • user operators
    • total supply
    • max supply
    • supported standards
  • Support of transfer_and_call mechanics
  • Support of finite allowance.

Actually, as far as the standard goes, it is pretty simple. Someone can create a tool that wraps the existing FA2 tokens into the FA2 that implements the new standard. So, the new protocols will only need to work with the newest tokens.

6 Likes

I agree completely! Storing JSON metadata on-chain is very awkward at the moment (especially with nested JSON), and likely one of the reasons it is so rarely used. Views do seem like a nice solution here?

1 Like

FA God-mode - Full custom foreign accept/reject logic for the transfer operation

I have given some thought to how to implement a sort of “god-mode” for token minters/rights administrators, while at the same time making transactions relatively safe for buyers.

This is how I imagine it could work:

  • An FA contract entry-point e.g. %transfer_fa3 would take a new list parameter - expected_ops - which represents the operations that the caller expects the contract to perform.
  • The purpose of expected_ops is to implement a handshake between caller and callee to determine whether or not they agree on the operations.
  • To determine if the expected_ops list is correct %transfer_fa3 would call a view on a contract that implements the verification logic. (The contract and view is configured by the token rights administrator.).
  • On approval from the foreign view %transfer_fa3 adds the operations in expected_ops.

So far this should be enough as long as the front-end knows the logic of the foreign contract view. However, to implement fully customizable logic I propose the following:

  • To ‘learn’ the requirements of the custom logic the %transfer_fa3 entry-point has another boolean parameter simulate_transfer, which is also passed on to the custom view.
  • In simulation mode the foreign contract view must build a string of the expected operations, put it on the stack and end execution using a FAILWITH.

The FAILWITH reveals what operations the custom logic expects. It is then up to the UX/UI to display that to the user in a readable format. In an advanced UI implementation the front-end could initiate an unpermissioned simulation.

Some backwards compatibility would be possible through a wrapper contract or entry-point that emits the correct expected_ops - however in this implementation the contract operations are emitted by %transfer_fa3 which is non-compliant.

There are a couple of catches; mainly that the rights of the buyer/collector may not be obvious. He/She can see what he needs to pay to obtain the token, but can not see the requirements of a second transfer. One consequence could be ‘cursed tokens’ which you can not remove from your wallet, so the FA contract should always allow burning the token. Another consequence is that the token owner may be limited by the contract in where/to whom he can sell the token. I propose that there is a configurable time period, maybe up to a maximum of 75 years after minting, whereafter the token owner can freely trade and transfer his tokens anywhere. There are probably many more concerns about the buyers that should be visible in the token contract through permission bits; however that is a topic for a deeper discussion as there a lot of details I have omitted.

There are a lot of things that need to be worked out for this scheme to work, for now I would be interested to hear what people think about the core idea?

This is to put the power in the hands of the token creators and let them decide how their token is used. All in the spirit of decentralization.

2 Likes

Thanks @KStasi for this summary , we needed it!

Concerning the transfer_and_call I have the impression that two things are missing:

  • a data field in bytes format to be able to tell the target contract why it is being called
  • the target contract must receive all the information about the transfer that led to the callback.

On the topic of the new specification, which I will call FA++, so we don’t mix it up with the FA3 in my previous post, and that we have an independent conversation.

I fully agree with the point on @mattdesl about modularity.
There are many issues with a standard that will contains all possible functionality. First of all, the standard will need to be updated every time one of the functionality needs to evolve. We may end up with a lot of standards, which is not good.
The standard will be more complicated to understand and implement.
And, when I will provide the implementation on Ligo, I expect users to just copy-paste the implementation in their project, or import the library. Then either they deploy it as is, with all the features that he doesn’t need. Or remove them/tweak them but then not be compliant with the standard anymore.

To sum up there is no “one token fits all need” and for this reason I think FA++ should be highly modular.
FA++ will just contains the minim for a token. That is a ledger and a tranfer entrypoint. possibly an on-chain view for the balance and token but that could be in other standards.
For the ledger, I’m not sure if we should have three possible types like in FA2 or only one. I think if we have one it will be suboptimal for anything other than multi-asset, or describe the ledger in separate standards, then they will be nothing left in FA++.
(notice that currently the transfert entrypoint is suboptimal in single-asset or NFT but is necessary to make all FA2 compatibles).

Then all other functionality, that is operators, on-chain views, integration view tickets, transfert and call, etc … could be in separate standard that are “plug-in” to FAx standards. A token would then be an union of those standards. And each standard could evolve independantly. Which means FA++ would probably stay around for a while.

For the modularity to works well, we probably have to pay attention about compatibility. Thus, FA++ should include what is needed to conect a token to a DEX.
So I believe only Transfer entrypoint and a few views. Maybe the Metadata? I would prefer metadata to be part of a separate specification like in FA2.

What do you guys think about this ?

1 Like