Resource Behaviors

New resources created with the ResourceBuilder can also be attributed with special characteristics. For example, you can specify behaviors to mark a resource as mintable, burnable, or even restrict its ability to be withdrawn, making them “Soulbound” tokens. AccessRules can also be specified with resource behaviors, which allows you to determine the conditions need to be present before the behavior action can be performed. We can also determine the mutability of these authorization rules to have the ability to change the authorization rules in the future or lock its mutability to prevent its rules from being changed.

Resource Behaviors

Defining access rules

Full details on the rule!(..) macro in the below is covered in Advanced AccessRules.

Method

Description

OwnerRole::None
OwnerRole::Fixed(rule!(..))
OwnerRole::Updatable(rule!(..))

The OWNER of a resource is set in the new_.. method of the resource builder and is by default able to update the metadata of the resource, and other rules can be set to forward to the OWNER role, as a useful fallback.

It is recommended to set this to some kind of dApp admin badge so that the metadata can be updated in future if required. This may be necessary as part of establishing two-way dApp linking during bootstrapping.

.mint_roles(mint_roles! {
    minter => rule!(..);         // Or => OWNER;
    minter_updater => rule!(..); // Or => OWNER;
})

Specify the AccessRule for MintRoles to provide permission to mint tokens.

Defaults: minter => rule!(deny_all) and minter_updater => rule!(deny_all).

.burn_roles(burn_roles! {
    burner => rule!(..);         // Or => OWNER;
    burner_updater => rule!(..); // Or => OWNER;
})

Specify the AccessRule for BurnRoles to provide permission to burn tokens.

Defaults: burner => rule!(deny_all) and burner_updater => rule!(deny_all).

.withdraw_roles(withdraw_roles! {
    withdrawer => rule!(..);          // Or => OWNER;
    withdrawer_updater => rule!(..);  // Or => OWNER;
})

Specify the AccessRule for WithdrawRoles to provide permission to withdraw tokens from a Vault.

Defaults: withdrawer => rule!(allow_all) and withdrawer_updater => rule!(deny_all).

.deposit_roles(deposit_roles! {
    depositor => rule!(..);         // Or => OWNER;
    depositor_updater => rule!(..); // Or => OWNER;
})

Specify the AccessRule for DepositRoles to provide permission to deposit tokens to a Vault.

Defaults: depositor => rule!(allow_all) and depositor_updater => rule!(deny_all).

.recall_roles(recall_roles! {
    recaller => rule!(..);          // Or => OWNER;
    recaller_updater => rule!(..);  // Or => OWNER;
})

Specify the AccessRule for RecallRoles to provide permission to recall tokens. By default recalling is locked as rule!(deny_all).

Defaults: recaller => rule!(deny_all) and recaller_updater => rule!(deny_all).

.freeze_roles(freeze_roles! {
    freezer => rule!(..);         // Or => OWNER;
    freezer_updater => rule!(..); // Or => OWNER;
})

Specify the AccessRule for FreezeRoles to provide permission to freeze tokens. By default freezing is locked as rule!(deny_all).

Defaults: freezer => rule!(deny_all) and freezer_updater => rule!(deny_all).

.non_fungible_data_update_roles(non_fungible_data_update_roles! {
    non_fungible_data_updater => rule!(..);
    non_fungible_data_updater_updater => rule!(..);
})

(Non-fungible only)

Specify the AccessRule for NonFungibleDataUpdateRoles to provide permission to update the NfData attached to each individual non-fungible.

Defaults: non_fungible_data_updater => rule!(deny_all) and non_fungible_data_updater_updater => rule!(deny_all).

.divisibility(number)

(Fungible only)

The divisibility is a number between 0 and 18 and it represents the number of decimal places that this resource can be split into. For example, if you set the divisibility to 0, people will only be able to send whole amounts of that resource.

Default: 18. You can also use the constants DIVISIBILITY_NONE = 0 and DIVISIBILITY_MAXIMUM = 18

.metadata(metadata! {
    init {
        "name" => "Super Admin Badge".to_string(), locked;
    }
})

Not strictly about behaviour on ledger, but you will likely wish to configure metadata as per the Metadata for Wallet Display and Metadata for Verification standards so that the resource has a clear identity in wallets and explorers.

Mintable

To make a resource mintable means that we allow the creation of additional supply of that resource. We can do this by simply adding .mint_roles() when we create our resource and map the AccessRule to each BurnRoles.

// Note our resource takes and OwnerRole argument this can be Fixed, Updatable, or None
let my_token = ResourceBuilder::new_fungible(OwnerRole::Fixed(rule!(require(access_rule))))
    .metadata(metadata!{
        init {
            "name" => "My Token", locked;
            "symbol" => "TKN", locked;
        }
    })
    .mint_roles(mint_roles!{ // #1
        minter => rule!(allow_all); // #2
        minter_updater => rule!(deny_all); // #3
    })
    .create_with_no_initial_supply();
  1. To make a resource mintable, you simply have to make a call to the mint_roles() method during the resource creation which requires that we map the AccessRule for two roles.

  2. Here we set the AccessRule for the minter role, allow_all makes minting public. We can of course and often will want to restrict minting to a particular badge here instead with something like minter => rule!(require(badge_address)); instead.

  3. The minter_updater is how we control the mutability of the minter role. deny_all locks the minter role, we can again also pass in a badge address to create an authority which can change the minter role like minter_updater => rule!(require(badge_address));

Burnable

Having a resource burnable indicates that an specified supply of this resource can essentially be destroyed. If all the supply of that resource is burnt, the ResourceManager will still exist. Additionally, if that resource is mintable then more of that resource can be created. Similar to our previous example, to make our resource burnable, we call the .burn_roles() method when we create our resource and map the AccessRule to each BurnRoles.

// Note our resource takes and OwnerRole argument this can be Fixed, Updatable, or None
let my_token = ResourceBuilder::new_fungible(OwnerRole::None)
    .metadata(metadata!{
        init {
            "name" => "My Token", locked;
            "symbol" => "TKN", locked;
        }
    })
    .burn_roles(burn_roles!{
        burner => rule!(allow_all); // This makes the resource freely burnable. You could also require(admin_badge) to restrict who can burn the token.
        burner_updater => rule!(deny_all);
    })
    .create_with_no_initial_supply();

Restrict Withdraw

Resources restricted from being withdrawn are effectively locked in the Vault that contains it. This makes the resource soulbound and its most common use-case is to attach some form of identification or reputation to the account that owns that resource.

// Note our resource takes and OwnerRole argument this can be Fixed, Updatable, or None
let my_token = ResourceBuilder::new_fungible(OwnerRole::Fixed(rule!(require(access_rule))))
    .metadata(metadata!{
        init {
            "name" => "My Token", locked;
            "symbol" => "TKN", locked;
        }
    })
    .withdraw_roles(withdraw_roles!{
        withdrawer => rule!(deny_all);
        withdrawer_updater => rule!(deny_all);
    })
    .create_with_no_initial_supply();

Restrict Deposit

Resources restricted from being deposited are commonly called transient resources. This forces a dangling resource to exist. If the resource can’t be deposited into a Vault, the resource must be burnt, else we will encounter a dangling resource error. Transient resources are most commonly used as a means to force a specified condition to happen within a transaction. If that condition is met, we can permit the resource to be burned. Alternatively, if we specify an authorization requirement, we can allow this resource to be deposited if a specified condition is met.

// Note our resource takes and OwnerRole argument this can be Fixed, Updatable, or None
let my_token = ResourceBuilder::new_fungible(OwnerRole::Fixed(rule!(require(access_rule))))
    .metadata(metadata!{
        init {
            "name" => "My Token", locked;
            "symbol" => "TKN", locked;
        }
    })
    .deposit_roles(deposit_roles!{
        depositor => rule!(deny_all);
        depositor_updater => rule!(deny_all);
    })
    .create_with_no_initial_supply();

Recallable Token

Having a resource to be recallable allows us to send our tokens to anybody, but have the ability for us to retrieve it if we desire. The most common use case for this is to allow for “Rental NFTs”. We can create conditions in how long this resource can essentially be borrowed for.

// Note our resource takes and OwnerRole argument this can be Fixed, Updatable, or None
let my_token = ResourceBuilder::new_fungible(OwnerRole::Fixed(rule!(require(access_rule))))
    .metadata(metadata!{
        init {
            "name" => "My Token", locked;
            "symbol" => "TKN", locked;
        }
    })
    .recall_roles(recall_roles!{
        recaller => rule!(require(admin_badge));
        recaller_updater => rule!(deny_all);
    })
    .create_with_no_initial_supply();

Freezable Token

When building regulated assets you may need to have the ability to freeze those assets so of course Scrypto has this functionality built in for you to compose for your own use case.

// Note our resource takes and OwnerRole argument this can be Fixed, Updatable, or None
let freezer_token =
    ResourceBuilder::new_fungible(OwnerRole::Fixed(rule!(require(access_rule))))
        .metadata(metadata! {
            init {
                "name" => "My Token", locked;
                "symbol" => "TKN", locked;
            }
        })
        .freeze_roles(freeze_roles! {
            freezer => rule!(require(admin_badge));
            freezer_updater => rule!(deny_all);
        })
        .mint_initial_supply(1000)
        .into();

A Non-fungible with updatable metadata

let non_fungible: NonFungibleResourceManager =
    ResourceBuilder::new_ruid_non_fungible::<TestNFData>(OwnerRole::None)
        .metadata(metadata! {
            init {
                "name" => "My NF Resource", locked;
            }
        })
        .non_fungible_data_update_roles(non_fungible_data_update_roles! {
            non_fungible_data_updater => rule!(require(admin_badge));
            non_fungible_data_updater_updater => rule!(deny_all);
        })
        .create_with_no_initial_supply()
        .into();

Updating the rules after creating resources

Up till now, we have specified all rules with an _updater rule set to rule!(denyall).

This means that it can never be changed, ever. Instead of rule!(deny_all) you could provide a custom rule with the usual rule!() macro. The authority you provide here has the ability to update the rule in the future. They can change the rule at will, and at any point they also have the right to change the _updater role’s rule to rule!(denyall), so that it may never again be changed. This means that it will display as fixed in the wallet.

Locking is a one-way process…​ there’s no going back to a mutable rule once it has been locked.

Here’s an example of playing with some rules around freezing a token:

// Initial creation, rule_admin is some badge address we have previously defined
let resource_address = ResourceBuilder::new_fungible(OwnerRole::Updatable(rule!(require(access_rule))))
  .metadata(metadata!(
    init {
        "name" => "Globally freezable token", locked;
    }
  ))
  .withdraw_roles(withdraw_roles! {
      withdrawer => rule!(allow_all);
      withdrawer_updater => rule!(require(rule_admin));
  })
  .deposit_roles(deposit_roles! {
      depositor => rule!(allow_all);
      depositor_updater => rule!(require(rule_admin));
  })
  .create_with_no_initial_supply();

... // Later in the code

// `rule_admin_vault` is a vault that contains the badge allowed to make the following changes.
self.rule_admin_vault.authorize(|| {
  // Freeze the token, so no one may withdraw or deposit it
  let resource_manager = ResourceManager::from_address(resource_address);
  resource_manager.set_depositable(AccessRule::DenyAll);
  resource_manager.set_withdrawable(AccessRule::DenyAll);

  // ...or, make it so only a person presenting the proper badge can withdraw or deposit
  resource_manager.set_depositable(rule!(require(transfer_badge)));
  resource_manager.set_withdrawable(rule!(require(transfer_badge)));

  // Unfreeze the token!
  resource_manager.set_depositable(AccessRule::AllowAll);
  resource_manager.set_withdrawable(AccessRule::AllowAll);

  // Lock the token in the unfrozen state, so it may never again be changed
  resource_manager.lock_depositable();
  resource_manager.lock_withdrawable();
});

The authorize() method

In the previous example, you saw the use of the authorize() method on the rule_admin_vault. This method is available on both Vaults and Buckets and is used to temporarily put a proof of the underlying resources on the authzone for authorization.

The method takes as parameter a closure with no argument and runs it after putting the proofs on the auth zone. After running the closure, the proofs are removed from the auth zone.

Default Rules

All roles have defaults they are set to when unspecified or set to None. They are:

Roles

Role Rule

Updater Role Rule

mint_roles

deny_all

deny_all

burn_roles

deny_all

deny_all

freeze_roles

deny_all

deny_all

recall_roles

deny_all

deny_all

withdraw_roles

allow_all

deny_all

deposit_roles

allow_all

deny_all

non_fungible_data_update_roles

deny_all

deny_all

  • For more info on the ResourceManager setting and updating methods check the Rust docs here

  • For additional methods you may also want to check out the ResourceManagerStub Rust docs here