Creating a "Regulated" Token

This example will walk you through the creation of a special token that has a 3-stage lifecycle, featuring different behavior at each stage. You’ll get a chance to familiarize yourself with how resources can have their behavior setup and changed later on and how the v0.4.0 authentication system ties into such a token.

You can find the code for this example on github here.

The blueprint we will build will perform several functions. It will create the token itself, it will sell the token to buyers, it will manage the token’s 3 stages of life, and it will permit the proper authority to toggle whether the token may be freely transferred.

Resources and Data

struct RegulatedToken {
    token_supply: Vault,
    internal_authority: Vault,
    collected_xrd: Vault,
    current_stage: u8,
    admin_badge_resource_address: ResourceAddress,
    freeze_badge_resource_address: ResourceAddress,
}

We’ll start with 3 vaults. token_supply will hold our "for sale" tokens, internal_authority will contain a badge that we’ll keep control of, and collected_xrd will hold our payments.

Our token has 3 stages, which we’ll track with an integer, and we’ll need to remember the ResourceAddress for a couple of badges which we’re going to give to the person instantiating the component. We will do something a little different in this example, in that the badges we’re returning will not be used solely to authorize access to a method…​they will have some control over the token we’re going to create, and their owners could exercise that control outside of our component if they so chose.

Getting Ready for Instantiation

To keep things simple, we won’t have any user-configurable instantiation parameters for our RegulatedToken, and upon instantiation we’ll return the component itself as well as two badges for administrative functions, giving us the following function signature:

pub fn instantiate_regulated_token() -> (ComponentAddress, Bucket, Bucket) {

We’re going to need 3 different badges with 3 distinct sets of permissions.

  1. A superadmin badge which has full permission to do anything.

  2. An internal admin badge which has the authority to mint more supply (when system rules permit), and bypass the restricted transfer (when it’s enabled).

  3. A badge used for selectively freezing the token supply.

When we go to create our regulated token using ResourceBuilder, we will have to specify any associated badges at creation time, so we must now create those badges.

// We will start by creating two tokens we will use as badges and return to our instantiator
let general_admin: Bucket = ResourceBuilder::new_fungible()
    .divisibility(DIVISIBILITY_NONE)
    .metadata("name", "RegulatedToken general admin badge")
    .burnable(rule!(allow_all), LOCKED)
    .initial_supply(1);

let freeze_admin: Bucket = ResourceBuilder::new_fungible()
    .divisibility(DIVISIBILITY_NONE)
    .metadata("name", "RegulatedToken freeze-only badge")
    .burnable(rule!(allow_all), LOCKED)
    .initial_supply(1);

// Next we will create a badge we'll hang on to for minting & transfer authority
let internal_admin: Bucket = ResourceBuilder::new_fungible()
    .divisibility(DIVISIBILITY_NONE)
    .metadata("name", "RegulatedToken internal authority badge")
    .burnable(rule!(allow_all), LOCKED)
    .initial_supply(1);

You’ll notice we are making each badge an indivisible singleton, and managing them directly. We expect that, in a real system, you would probably make use of a purpose-built blueprint for creating and managing them.

Now we’re ready to make our token! We’ll show the code first, and then explain it in detail below:

llet access_rule: AccessRule = rule!(
    require(general_admin.resource_address())
        || require(internal_admin.resource_address())
);
let my_bucket: Bucket = ResourceBuilder::new_fungible()
    .divisibility(DIVISIBILITY_MAXIMUM)
    .metadata("name", "Regulo")
    .metadata("symbol", "REG")
    .metadata(
        "stage",
        "Stage 1 - Fixed supply, may be restricted transfer",
    )
    .updateable_metadata(
        access_rule.clone(), (1)
        MUTABLE(access_rule.clone()) (2)
    )
    .restrict_withdraw(
        access_rule.clone(), (1)
        MUTABLE(access_rule.clone()) (2)
    )
    .mintable(
        access_rule.clone(), (1)
        MUTABLE(access_rule.clone()) (2)
    )
    .initial_supply(100);
1 Both the general_admin and internal_admin have the authority to update the metadata for the token, mint the token, and withdraw the tokens from vaults.
2 Both the general_admin and internal_admin have the authority to update the following rules in the future: updatable_metadata, restricted_withdrawal, and mutable in the future.

There’s a lot to go over here. Let’s first go through what the intention behind this is and then move to code, the intention is to create a resource which may be minted, withdrawn from a vault, or have its metadata updated by any entity(entities) which has either the admin badge or the internal admin badge, additionally, we would like for this entity(entities) to be able to update the above-mentioned behavior in the future. As an example, we would like for the general_admin to be able to say that the resource may no longer be minted.

We first begin by defining a rule that says that for some action, either the general_admin or the internal_admin badges are required. We defined this rule here as it will be used a lot when we’re creating the token’s resource.

We then begin to create the token’s resource through the ResourceBuilder. We setup some metadata on the resource and then we begin defining the resource behavior. As of v0.4.0, the resource behavior is no longer defined through flags. Instead, we use system-level authentication and rules to specify the behavior of the resource. Now comes the exciting part where we need to specify how the resource behaves and who is allowed to perform what. We will explain how this is achieved for the minting of the resource as the other behavior is defined in an identical way.

// The resource builder and metadata goes here. Stripped for simplicity
.mintable(
    access_rule.clone(), (1)
    MUTABLE(access_rule.clone()) (2)
)
1 This portion of the code here specifies that the access_rule applies on the minting of the resource. We’ve defined the access_rule above to be a general_admin badge or an internal_admin badge. This means that this resource can be minted if an entity is in the possession (and chooses to present) of a general_admin or an internal_admin badge.
2 This portion of the code here specifies that the access_rule applies on who can change the minting behavior of the resource. We’ve defined the access_rule above to be a general_admin badge or an internal_admin badge. This means that this resource have it’s minting behavior changes (i.e. continued, resumed, locked, and so on) if an entity is in the possession (and chooses to present) of a general_admin or an internal_admin badge. This is essentially the equivalent of specifying that the MINTABLE flag from v0.3.0 is mutable.

The other resource behavior that we’ve specified when creating the resource works in an identical way to what we’ve described above. For stage 1 of our token, we will start with the token in a restricted transfer state (where the proper authority must be present in order to withdraw it from a Vault), and we will allow the metadata to be updatable (again, when the proper authority is present).

Finally, we create our supply of 100 tokens and place them in a Bucket. Whew! That may seem overwhelming on first read, but after you’ve done it a couple times, you’ll sail through it with hardly a thought.

We now want to setup the authentication rules of the methods on our component. Before looking into code, let’s define what is it that we’re trying to achieve, and then we can take a look at the code required to achieve that. We would like for the general_admin and the freeze_admin to have the authority to toggle the transfer freeze by calling toggle_transfer_freeze. We would also like to give the general_admin badge the authority to collect payments and advance the current stage of the token through the collect_payments and advance_stage methods. We do not wish to put any authentication rules on any other method, meaning that we would like for all other methods to be freely callable. Translating this into code, we get the following:

// Next we need to setup the access rules for the methods of the component
let access_rules: AccessRules = AccessRules::new()
    .method( (1)
        "toggle_transfer_freeze",
        rule!(
            require(general_admin.resource_address())
                || require(freeze_admin.resource_address())
        ),
    )
    .method( (2)
        "collect_payments",
        rule!(require(general_admin.resource_address())),
    )
    .method( (3)
        "advance_stage",
        rule!(require(general_admin.resource_address())),
    )
    .default(rule!(allow_all)); (4)
1 Both the general_admin and the freeze_admin have the authority to toggle the freeze on token transfers by calling the toggle_transfer_freeze method. Anybody without these badges would be unable to call this method.
2 Only the general_admin is allowed to collect the payments made to the component through the collect_payments method. Anybody without this badge would be unable to call this method.
3 Only the general_admin is allowed to advance the stage of the token through the advance_stage method. Anybody without this badge would be unable to call this method.
4 Other methods have no restrictions and may be called by anybody.

All that’s left now is to actually instantiate our component, plug in the access rules created above, and then return it along with the badges we just created.

let mut component = Self {
    token_supply: Vault::with_bucket(my_bucket),
    internal_authority: Vault::with_bucket(internal_admin),
    collected_xrd: Vault::new(RADIX_TOKEN),
    current_stage: 1,
    admin_badge_resource_address: general_admin.resource_address(),
    freeze_badge_resource_address: freeze_admin.resource_address(),
}
.instantiate();
component.add_access_check(access_rules);

(component.globalize(), general_admin, freeze_admin)

Freezing or unfreezing token transfers

Anyone in possession of either the superadmin badge or the freeze badge will have the ability to put the token into or out of a restricted transfer state. Let’s see how to accomplish that.

pub fn toggle_transfer_freeze(&self, set_frozen: bool) {
    // Note that this operation will fail if the token has reached stage 3 and the token behavior has been locked
    let token_resource_manager: &mut ResourceManager =
        borrow_resource_manager!(self.token_supply.resource_address());

    self.internal_authority.authorize(|| {
        if set_frozen {
            token_resource_manager.set_withdrawable(rule!(
                require(self.admin_badge_resource_address)
                    || require(self.internal_authority.resource_address())
            ));
            info!("Token transfer is now RESTRICTED");
        } else {
            token_resource_manager.set_withdrawable(rule!(allow_all));
            info!("Token is now freely transferrable");
        }
    })
}

Based on the set_frozen boolean, we will either allow everybody to withdraw this token from vaults they have access to, or only allow the internal admin and the general admin to perform withdrawals.

On any resource, the state of all resource behavior and its mutability is always publicly shown. This permits implementors of both Scrypto blueprints as well as tools like wallets to understand not only what a resource’s current behavior is, but also what it may be in the future. In other words, a wallet could give a user a clear warning that a token is currently freely transferrable, but the withdrawable behavior is mutable, and hence there is no guarantee that it will always be freely transferrable.

Advancing the token to the next stage

There’s one more workhorse function to our blueprint, which is the logic to advance it through the next 2 stages of behavior. We’ll reproduce it here, and then break down what’s happening.

pub fn advance_stage(&mut self) {
    // Adding the internal admin badge to the component auth zone to allow for the operations below
    ComponentAuthZone::push(self.internal_authority.create_proof());

    assert!(self.current_stage <= 2, "Already at final stage");
    let token_resource_manager: &mut ResourceManager =
        borrow_resource_manager!(self.token_supply.resource_address());

    if self.current_stage == 1 {
        // Advance to stage 2
        // Token will still be restricted transfer upon admin demand, but we will mint beyond the initial supply as required
        self.current_stage = 2;

        // Update token's metadata to reflect the current stage
        let mut metadata = token_resource_manager.metadata();
        metadata.insert(
            "stage".into(),
            "Stage 2 - Unlimited supply, may be restricted transfer".into(),
        );
        token_resource_manager.update_metadata(metadata);

        // Enable minting for the token
        token_resource_manager
            .set_mintable(rule!(require(self.internal_authority.resource_address())));
        info!("Advanced to stage 2");

        // Drop the last added proof to the component auth zone which is the internal admin badge
        ComponentAuthZone::pop().drop();
    } else {
        // Advance to stage 3
        // Token will no longer be regulated
        // Restricted transfer will be permanently turned off, supply will be made permanently immutable
        self.current_stage = 3;

        // Update token's metadata to reflect the final stage
        let mut metadata = token_resource_manager.metadata();
        metadata.insert(
            "stage".into(),
            "Stage 3 - Unregulated token, fixed supply".into(),
        );
        token_resource_manager.update_metadata(metadata);

        // Set our behavior appropriately now that the regulated period has ended
        token_resource_manager.set_mintable(rule!(deny_all));
        token_resource_manager.set_withdrawable(rule!(allow_all));
        token_resource_manager.set_updateable_metadata(rule!(deny_all));

        // Permanently prevent the behavior of the token from changing
        token_resource_manager.lock_mintable();
        token_resource_manager.lock_withdrawable();
        token_resource_manager.lock_updateable_metadata();

        // With the resource behavior forever locked, our internal authority badge no longer has any use
        // We will burn our internal badge, and the holders of the other badges may burn them at will
        // Our badge has the allows everybody to burn, so there's no need to provide a burning authority

        // Drop the last added proof to the component auth zone which is the internal admin badge
        ComponentAuthZone::pop().drop();

        self.internal_authority.take_all().burn();
        info!("Advanced to stage 3");
    }
}

When we advance to stage 2, we will update our metadata to indicate to the world that we’re at the next stage, and permit minting to occur in order to satisfy demand beyond the initial supply.

In order to update our "stage" metadata, we first get a copy of the existing metadata, and then use insert, which functions like an "upsert", to update the value we’re interested in. We then set our updated metadata to be written to the token’s ResourceManager, which is possible because the withdrawable resource behavior has been allowed at this stage.

Since this method will require auth at multiple points, we chose to put the internal_admin badge in the component Auth-Zone and then take it out at the end of the method. This means that all of the operations allowed for the internal_admin are now allowed within this method.

Advancing to stage 3 works similarly to advancing to stage 2, except this time we are effectively "dumbing down" the token now that the regulated period has ended. We no longer wish for the token to have mutable behavior (example: we no longer want the minting of the token to be paused and continued.); so, we first set the behavior of the token to the behavior we wish for it to be locked in, then we call the lock_mintable, lock_withdrawable, and lock_updateable_metadata methods on the token’s resource manager to forever lock the current behavior in place and disable it’s future changed.

Locking the behavior is a one-way operation! Once it has been made immutable, you can never go back.

Finally, since our internal authority badge was only used for minting and bypassing restricted transfer rules, we can safely destroy it. We have configured the internal_admin to be burnable by anybody who holds it, so we can freely burn it.

Closing thoughts

We did some things you probably wouldn’t want to do in a real system.

  • We blindly try to do some things that may be impermissable based on the current state of the system, to allow you to experience the appropriate runtime errors if you’re playing with the example code. In a real system, it would be better to note that the operation was about to fail, and kick out an error explaining the situation.