sRFC 37: Efficient Block/Allow List Token Standard

sRFC 37: Efficient Block/Allow List Token Standard

Summary

This proposal aims to introduce a novel mechanism of permissioned tokens without the drawbacks of the existing solutions. By following this specification, issuers can create permissioned tokens using Token22, the Default Account State extension and an allow/block listing smart contract with delegated freezing authority.

Context

Permissioned tokens fall into one of three use cases:

  1. As an issuer I want to block X users
  2. As an issuer I want to allow Y users
  3. As an issuer I need to execute some custom logic in order to allow Z users to transact

This proposal targets use cases 1 and 2, these are the permissioning happy-paths that should have better UX without compromising performance.

Background

Permissioned tokens in solana, before Token22, were based on wrapper programs that would thaw/freeze token accounts during each user interaction, at the cost of UX.

Token22 aimed to introduce alternatives while maintaining UX. The transfer-hook extension has a standardized interface that enables everyone to transfer and still execute custom code without requiring specialized UIs.

Even though this fixes the wallet UX this comes with great cost to protocol developers as it adds friction in the form of overhead compute units used during transfers and account dependency hell. This complexity leads most protocols simply blacklisting all token Mints with the transfer-hook extension.

Alternatively, issuers can use the Default Account State (DAS) extension to create permissioned tokens. This alternative trades UX for DX. Developer experience and composability are maintained, but user experience becomes significantly degraded. Token holders require the issuers manual intervention to thaw their token accounts before interacting with protocols. The issuers need to constantly thaw token accounts for their users, this is specially bothersome when related to sanctions lists where issuers only care about blocking some users.

Proposal

A new mechanism that borrows experience from previous methods and uses the DAS extension along with a Smart Contract (hereon referred to by Freeze Authority Management Program or FAMP for short) delegated freeze authority. Additionally a second, user defined, Smart Contract that implements a specific interface with instructions that gate the ability of the previous one from permissionlessly calling the freeze and thaw functions on Token22 for a given Token Account. This approach borrows from the widely controversial transfer-hook workflow without compromising token transfer user and developer experience - it only checks whether the FAMP should be able to permissionlessly thaw or freeze a TA.

The novelty in this workflow is the removed issuer friction of having to manually thaw every single token account without sacrificing composability and transaction compute usage for most allow/block listing scenarios. The only assumption is that there may be some on-chain record that enables the thawing gating business logic to allow or block a given wallet from permissionlessly thawing their TAs.

Additionally, with the freeze authority, it’s easy for issuers to revoke authorization anytime by freezing a user’s token account.

The Freeze Authority Management Program will have a canonical implementation (like the Token and Token22 programs), issuers who want to use this mechanism only need to write or use a single Smart Contract that implements the interface. When using the interface, the implementation decides whether a given method is supported or not, and how to behave if not supported - always accept or fail these instruction calls.

The FAMP will still allow a regular defined Freeze Authority that is kept under control of the issuer and the user defined Smart Contract that gates the permissionless functions only has the ability to fail those transactions. This means that issuers can use a 3rd party created allow or block list and still remain in full control of their authorities without compromising any other functionality or authority.

Specification

Token Program

This standard requires a Token22 based token as it depends on the Default Account State extension.

The token needs to delegate the Freeze Authority to the Freeze Authority Management Program described in the next section.

Freeze Authority Management Program

The Freeze Authority Management Program is a smart contract that augments the capabilities of the freeze authority for a given token. This new program not only maintains the ability to freeze and thaw tokens using an issuer defined freeze authority but this also introduces the capability for permissionless thawing and permissionless freezing of token accounts by using an issuer defined Smart Contract with gating business rules.

In order for the Freeze Authority Management Program to work, it requires that issuers delegate their freeze authority over. Given that freezing and thawing is such an important part of RWA workflows, the program maintains the same baseline features.

The new permissionless features are a means for anyone to be able to thaw or freeze token accounts when issuers use DAS extension on Token22 Token mints. These new permissionless instructions will call certain functions of a freeze authority defined Smart Contract that is responsible for deciding whether a Token Account should be frozen or thawed.

Using either the permissionless thaw and/or the permissionless freeze should be optional and defined by the freeze authority. This enables greater flexibility and allows the user defined Smart Contract to be an allow or block list operated by a 3rd party independent of the token issuer and freeze authority.

In order to maintain a secure environment, the Freeze Authority Management Program should ensure that permissionless instructions de-escalate account permissions when calling into the user defined code to prevent abuse from bad actors.

Accounts

MintConfig

Is a PDA that stores configurations and is going to be the delegated freeze authority for a given token mint.

PDA derivation: [b“MINT_CFG”, mint_address]

Discriminator: u8 = 0x01

Structure:

  • mint: Pubkey
    • The mint this MintConfig is associated with. Even though this could be handled by PDA derivation, it’s easier for fetching and discovery.
  • authority: Pubkey
    • User defined authority capable of changing the gating program and calling the permissioned instructions
  • gating_program: Pubkey
    • User defined program. Pubkey::default() for none.
  • enable_permissionless_thaw: bool
    • Whether or not to enable the permissionless thaw for a given token
  • enable_permissionless_freeze: bool
    • Whether or not to enable the permissionless freeze for a given token

Instructions

  • set_authority

    • Allows changing the given authority on a MintConfig account
  • create_config

    • Creates a MintConfig account
    • Can only be called once per Mint
    • Optionally safely sets the freeze authority in the given mint (needs freeze authority to call as signer)
  • set_gating_program

    • Changes the MintConfig.gating_program.
  • forfeit_freeze_authority

    • Transfers the mint freeze authority back to the freeze authority
  • thaw (permissioned)

    • Given that the program holds the freeze authority, it needs to implement a regular permissioned thaw. Only callable by MintConfig.authority.
  • freeze (permissioned)

    • Given that the program holds the freeze authority, it needs to implement a regular permissioned freeze. Only callable by MintConfig.authority.
  • thaw_permissionless

    • Calls the gating instruction to decide whether or not the caller should be able to thaw a token account permissionless
  • freeze_permissionless

    • Calls the gating instruction to decide whether or not the caller should be able to freeze a token account permissionless

Interface

The interface needs two methods, both with optional implementations (should return an error when not implemented). Each implemented instruction requires the respective extra account metas PDA created and populated in order to enable account dependency resolution:

  • Permissionless thaw
    • Discriminator_hash_input: “efficient-allow-block-list-standard:can-thaw-permissionless”
    • Discriminator: [u8; 8] = [8, 175, 169, 129, 137, 74, 61, 241]
    • Extra Accounts Metas seeds: [b”thaw-extra-account-metas”, mint_address]
    • Remaining instruction data: [ ]
    • Accounts: [caller, token account, mint, extra-account-metas]
    • Remaining accounts: accounts as defined in extra account metas PDA
  • Permissionless freeze
    • Discriminator_hash_input: “efficient-allow-block-list-standard:can-freeze-permissionless"
    • Discriminator: [u8; 8] = [214, 141, 109, 75, 248, 1, 45, 29]
    • Extra Account Metas seeds: [b”freeze-extra-account-metas”, mint_address]
    • Remaining instruction data: [ ]
    • Accounts: [caller, token account, mint, extra-account-metas]
    • Remaining accounts: accounts as defined in extra account metas PDA

TBD: should more accounts be passed in? (TA owner can be listed as a dependency in extra account metas, issuer defined Freeze Authority requires a few extra accounts - FAMP + MintConfig PDA)

Extra accounts format: libraries/tlv-account-resolution at main · solana-program/libraries · GitHub

Unlike the transfer-hook interface, we’re not providing interface instructions to populate the extra account metas given that this is widely dependent on the protocol and user implementation.

User Defined Smart Contract

The user defined Smart Contract in this workflow is responsible for implementing the interface instructions. The instructions themselves will not call the T22 to freeze/thaw, but simply check whether the caller should freeze or thaw.

For each of the thaw and freeze instructions, the smart contract also needs to create and populate the respective extra metas account.

The instructions should return an error value when the given operation is not supported, not valid, or doesn’t pass all checks to occur in a permissionless manner.

Here are some common workflows and how to execute them:

Permissionless thaw

  • This TA owner is blocked from interacting with my token?
    • Yes: Return failure
    • No: return success
  • This operation is supported permissionlessly in my contract?
    • Yes: execute other checks
    • No: return failure
  • This TA owner is allowed to interact with my token?
    • Yes: return success
    • No: return failure

Permissionless freeze

  • This TA owner is blocked from interacting with my token?
    • Yes: Return success
    • No: return failure
  • This operation is supported permissionlessly in my contract?
    • Yes: execute other checks
    • No: return failure
  • This TA owner is allowed to interact with my token?
    • Yes: return failure
    • No: return success

SDKs

TypeScript

The typescript SDK should be able to:

  • Detect whether a mint uses this standard or not
  • Be able to craft a permissionless thaw instruction solely from the mint address
  • Be able to craft a permissionless freeze instruction solely from the mint address
  • Implement the methods to thaw and freeze permissioned
  • Support the remaining methods to handle initialization and authority management

Rust

The rust SDK should implement a similar functionality compared to the transfer-hook interface with an on-chain and an off-chain component.

The on-chain component serves to help the proxy program to parse the extra-account-metas and build the respective CPI into the user-defined program, while the off-chain component serves to help build transactions from off-chain rust programs.

Reference: transfer-hook/interface at main · solana-program/transfer-hook · GitHub

Workflow

Link to google docs with workflow diagrams on client discovery and transaction execution: sRFC 37: Efficient Block/Allow List Token Standard - Google Docs

Security

The Freeze Authority Management Program solves the largest security concern in this system - the ability for a 3rd party to insert malicious instructions in unsuspecting users transactions. Standardizing a way for wallets/contracts/client software to introduce a new instruction to thaw token accounts right after creation is a sure way to enable bad actors.

The Freeze Authority Management Program solves this by de-escalating the permissions and acting as a proxy into the actual custom code that decides whether or not to act on the permissionless thaw and freeze operations.

Implementations

Ongoing implementation: GitHub - tiago18c/ebalts

4 Likes

Superstate has successfully integrated this style of a “permissionless thaw allowlist” with a couple of DeFi protocols, also with an aim to make this a standardized approach to simplify integration with future protocols and issuers who want to use this pattern. From our experience, this has been a much easier integration than attempting to do a transfer hook style integration.

For Superstate to integrate with FAMP, we need the Token Account (TA) owner to be included within the account list of “Permissionless Thaw” and “Permissionless Freeze”, as this account is required to derive some of our internal PDAs that determine whether a TA is allowed or blocked.

Moreover, we need the ability to store extra account metadata using the tlv-account-resolution library (specifically libraries/tlv-account-resolution/src/seeds.rs at main · solana-program/libraries · GitHub ) so that the “Extra Accounts” can load the aforementioned PDAs derived from the TA owner. I doubt the FAMP would block this in any way, but I just wanted to explicitly call it out.

Lastly, I had a curiosity on the stated goals vs. the implementation chosen and wanted to seek clarification. In the background, you mention that the transfer hook interface leads protocols to have “account dependency hell”. I interpret this to mean its usage of the “Extra Accounts Metas” pattern, and see that you also have chosen this approach. But perhaps it does not suffer from the same fate because implementors of the transfer hook extension can include arbitrary code, whereas the goal of FAMP is to have a very constrained use case for custom code?

Thanks folks! The only question I have at the moment is a use-case for permissionless freeze. What real-world use case we want to solve with this operation?

Permissionless freeze would be used in a token where we want a block list, e.g. a sanctions list.

In this scenario, the Block List authority needs to set a given wallet as blocked - e.g. creating a PDA record for a given wallet - and then can sweep all Token Accounts that are owned by this wallet a call permissionless freeze for those. The permissionless freeze gate instructions should then check if the PDA block record exists to allow the instruction to succeed.

This makes the process much easier, specially if the authority is behind some multisig or MPC wallet with manual controls.

We can easily get it in the Extra Account metas. For ease of use and given that it will be used frequently, I think it makes sense to be added as part of the baseline accounts.

The User Defined Program has the responsibility of creating and managing the TLV PDA. The FAMP will only decode it to ensure all accounts are provided and order them correctly before executing the CPI.

The largest goal was to shift the permissioning logic from the transfer execution time (by using transfer-hooks), to somewhere else. For protocols, this means not having to deal with extra accounts during transfers and heavily reduced CU usage compared to transfer-hooks (which is going to be even further reduced with the possible rewrite of T22 using pinocchio).

1 Like