The Regulated Token Example

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 auth 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,
    collected_xrd: Vault,
    current_stage: u8,
    admin_badge_address: ResourceAddress,
}

We’ll start with 3 vaults. token_supply will hold our "for sale" tokens 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 our admin badge 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.

Defining Component Auth

We will need to define two roles to divide responsibilities:

  • general_admin - Role used for general logistics of the token and component.

  • freeze_admin - Role used solely for freezing transfers, vaults, and recalling tokens if necessary.

We will also restricted methods to the appropriate role:

enable_method_auth!{
    roles {
        freeze_admin => updatable_by: [];
        general_admin => updatable_by: [];
    },
    methods {
        toggle_transfer_freeze => restrict_to: [freeze_admin];
        collect_payments => restrict_to: [general_admin];
        advance_stage => restrict_to: [general_admin];
        get_current_stage => PUBLIC;
        buy_token => PUBLIC;
    }
}

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() -> (Global<RegulatedToken>, Bucket, Bucket) {

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

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

  2. A freeze admin badge for the freeze_admin role.

  3. A general admin badge for the general_admin role.

We will also be using specify an actor virtual badge for the component which has the authority to mint more supply (when system rules permit), and bypass the restricted transfer (when it’s enabled).

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 are allocating a ComponentAddress used for our actor virtual badge and provide minting & transfer authority to our component
let (address_reservation, component_address) =
    Runtime::allocate_component_address(Runtime::blueprint_id());

// Creating two resources we will use as badges and return to our instantiator
let general_admin: Bucket = ResourceBuilder::new_fungible(OwnerRole::None)
    .divisibility(DIVISIBILITY_NONE)
    .metadata(metadata! (
        init {
            "name" => "RegulatedToken general admin badge", locked;
        }
    ))
    .burn_roles(burn_roles!(
        burner => rule!(allow_all);
        burner_updater => rule!(deny_all);
    ))
    .mint_initial_supply(1);

let freeze_admin: Bucket = ResourceBuilder::new_fungible(OwnerRole::None)
    .divisibility(DIVISIBILITY_NONE)
    .metadata(metadata! (
        init {
            "name" => "RegulatedToken freeze-only badge", locked;
        }
    ))
    .burn_roles(burn_roles!(
        burner => rule!(allow_all);
        burner_updater => rule!(deny_all);
    ))

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:

let access_rule: AccessRule = rule!(
    require(general_admin.resource_address())
        || require(global_caller(component_address))
);
let regulated_tokens: Bucket = ResourceBuilder::new_fungible(OwnerRole::None)
    .divisibility(DIVISIBILITY_MAXIMUM)
    .metadata(metadata! (
        roles {
            metadata_setter => access_rule.clone();
            metadata_setter_updater => access_rule.clone();
            metadata_locker => access_rule.clone();
            metadata_locker_updater => access_rule.clone();
        },
        init {
            "name" => "Regulo", locked;
            "symbol" => "REG", locked;
            "stage" => "Stage 1 - Fixed supply, may be restricted transfer", updatable;
        }
    ))
    .freeze_roles(freeze_roles!( (1)
        freezer => rule!(require(freeze_admin.resource_address()));
        freezer_updater => access_rule.clone(); (2)
    ))
    .withdraw_roles(withdraw_roles!( (3)
        withdrawer => rule!(require(freeze_admin.resource_address()));
        withdrawer_updater => access_rule.clone();
    ))
    .recall_roles(recall_roles!( (4)
        recaller => access_rule.clone();
        recaller_updater => access_rule.clone();
    ))
    .mint_roles(mint_roles!( (5)
        minter => rule!(deny_all);
        minter_updater => access_rule.clone();
    ))
    .mint_initial_supply(100);
1 Mapping the AccessRule of the freezer to the freeze admin badge to allow the freeze_admin to freeze user’s Vault when a user has committed activities against regulation.
2 Resource _updater role is configured to either be the general_admin or the component itself to update resource behavior roles if necessary.
3 Mapping the AccessRule of the withdrawer to the freeze admin badge to allow the freeze_admin to restrict token transfers.
4 Mapping the AccessRule of the recaller to the freeze admin badge to allow the freeze_admin to recall tokens from user’s Vault when a user has committed activities against regulation.
5 Mapping the AccessRule of the minter to deny_all to restrict additional minting, but this can later be updated unless the _updater role is locked.

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, freeze the token inside a user’s Vault, recall the token from a user’s Vault, withdrawn from a Vault, or have its metadata updated by any entity(entities) which has either the admin badge or the component itself, 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 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. 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.

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.

All that’s left now is to actually instantiate our component, mapping the roles to their AccessRule, and then return it along with the badges we just created.

let component = Self {
    token_supply: Vault::with_bucket(regulated_tokens),
    collected_xrd: Vault::new(RADIX_TOKEN),
    current_stage: 1,
    admin_badge_address: general_admin.resource_address(),
    freeze_admin_badge_address: freeze_admin.resource_address(),
}
.instantiate()
.prepare_to_globalize(OwnerRole::None)
.roles(
    roles!(
        freeze_admin => rule!(require(freeze_admin.resource_address()));
        general_admin => rule!(require(general_admin.resource_address()));
    )
)
.with_address(address_reservation)
.globalize();

(
    component,
    general_admin,
    freeze_admin,
)
}

Freezing or unfreezing token transfers

The freezer_admin role that has possession of 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 =
        self.token_supply.resource_manager();

    if set_frozen {
        token_resource_manager.set_withdrawable(rule!(
            require(self.freeze_admin_badge_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) {

    assert!(self.current_stage <= 2, "Already at final stage");
    let token_resource_manager =
        self.token_supply.resource_manager();

    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
        token_resource_manager
            .set_metadata(
                "stage",
                "Stage 2 - Unlimited supply, may be restricted transfer".to_string(),
            );

        // Enable minting for the token
        token_resource_manager
            .set_mintable(rule!(
                require(self.admin_badge_address)
                    || require(global_caller(Runtime::global_address()))
            ));
        info!("Advanced to stage 2");

    } 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
        token_resource_manager
            .set_metadata(
                "stage",
                "Stage 3 - Unregulated token, fixed supply".to_string(),
            );

        // Set our behavior appropriately now that the regulated period has ended
        token_resource_manager.set_mintable(rule!(deny_all));
        token_resource_manager.set_freezeable(rule!(deny_all));
        token_resource_manager.set_recallable(rule!(deny_all));
        token_resource_manager.set_withdrawable(rule!(allow_all));
        token_resource_manager.set_metadata_role("metadata_setter", rule!(deny_all));
        token_resource_manager.set_metadata_role("metadata_setter_updater", 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_freezeable();
        token_resource_manager.lock_recallable();
        token_resource_manager.lock_updatable_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

        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.

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, lock_freezeable, lock_recallabe, and lock_updatable_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.

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.