TZIP-15 Token Transferlist Interface: request for comment


TZIP-15 is a new specification that proposes a standard for a transfer filtering interface, a common requirement for token contracts which represent real-world financial assets (e.g. securities). The idea of the interface is to propose a lightweight permission schema, that takes the form of user transferlists, i.e. lists of who is authorized to send and/or receive a transfer. For token contracts which use this mechanism, it is impossible for a user to send or receive transfers without being listed on the transferlist.

A transferlist (in other contexts known as a “whitelist”) is a list of pairs of userIds that are allowed to send and receive tokens, respectively. A mechanism is provided to assert that each transfer is allowed by the transferlist before it completes. A block of transfers can be asserted as a whole and will be rejected if one of the senders or receivers doesn’t belong to the transferlist.

A transferlist can be either restricted or unrestricted. The parameter unrestricted is a boolean; when it is set to False, the transferlist is restricted, and its user will fail the asserts (i.e. assertReceivers and assertTransfers).

Alternately, a restricted transferlist will generally behave as if its allowedTransferlists is empty.

This interface can either be used as part of a token contract (i.e. in a monolith configuration) or as a separate contract that is queried on demand.

Summary of the entrypoints

Here are the 8 entrypoints defined in the standard, grouped by category:


  • assertReceivers
    Succeed if each userId in the list is associated to a transferlist that can receive transfers.
  • assertTransfers
    Succeed if each pair of from/to userId’s is associated with transferlists that may send to/receive from each other, respectively.


  • setIssuer
    Set the issuer’s userId
  • updateUser
    Add, update, or remove a user
  • updateTransferlist
    Add, update, or remove a transferlist


  • getIssuer
    Get the issuer’s userId
  • getUser
    Get user’s transferlistId if the userId exists in users, returns None otherwise
  • assertTransferlist
    Succeed if the given transferlistId exists in transferlists, the given unrestricted bool matches its unrestricted state, and the given allowedTransferlists is a subset of its allowedTransferlists

Transferlist Examples

To illustrate how the transferlist works, here is an example of an assetTransfers call with several users and transferlists.

Consider the following setup where userId is a string and transferlistId is an integer:


"alice": 0

"bob": 0

"charlie": 1

"dan": 2


0: (unrestricted: True, allowedTransferlists: {0, 2})

1: (unrestricted: True, allowedTransferlists: {1})

2: (unrestricted: False, allowedTransferlists: {1, 2})

Then suppose the following call to assertTransfers were made:


{ Pair "alice" { "bob", "dan" }

, Pair "bob" { "alice" }

, Pair "charlie" { "charlie", "dan" }

  1. alice → bob
    Alice and Bob are on the same transferlist (0), which contains itself in its allowedTransferlists and is unrestricted, so this succeeds.

  2. alice → dan
    Alice is on a transferlist (0) that contains Dan’s transferlistId (2) in its allowedTransferlists and is unrestricted, but it fails because Dan’s transferlist is restricted.

  3. bob → alice
    This succeeds by the same logic as Alice → Bob: they’re on the same unrestricted transferlist that contains its own transferlistId in its allowedTransferlists.

  4. charlie → charlie
    This succeeds since Charlie’s transferlist is unrestricted and contains its own transferlistId in its allowedTransferlists.

  5. charlie → dan
    This fails because Dan’s transferlist (2) is restricted.

Thus the above call to assertTransfers will fail.

Diagramming the Transferlist interface

Request for comment

The full specifications are published on TZIP under TZIP-15, and we are now requesting comments from the Tezos community.

Below are a few questions that we are eager to discuss:

  • updateTransferList is optimized for potentially-large allowed outbound sets, i.e. it accepts granular patches, but it could be simplified if all allowed outbound sets are small. What are some use cases for large outbound sets?
  • If a canonical userId for the issuer were specified, the setIssuer and getIssuer entrypoints could be removed. Would this be better than explicitly distinguishing the issuer on-chain?
  • What off-chain tools would be helpful to administrate a transferlist contract?

If you have any questions, feedback, or comments about this new specification, please submit them in the section below, or directly on the GitLab repo. The core behavior is implemented in Lorentz, SmartPy, and partially in LIGO. You can find the links at the bottom of the blog post.

Next Steps


A tutorial on how to use the transferlist is in development and will be added to the Assets portal soon.


Some implementations of transferlist contracts are currently under development:

  • An implementation of compile-time wrapping and separate-contract transferlists in Lorentz can be found here
  • A partial implementation of a compile-time wrapper in LIGO can be found here
  • An implementation of the separate-contract transferlist in SmartPy can be found here

Thanks to the teams at Neofacto, Tezos Paris Hub, and Serokell for their feedback on TZIP-15 to date.