The user badge pattern

On Ethereum and other smart contract platforms, developers usually keep track of users with their wallet addresses. For example, many smart contracts define who has the right to do certain things by storing the user’s wallet address in a list. Doing authorization that way is less flexible and often creates vulnerabilities. What if the user wants to cede their access to another user ? For every authorization change, the smart contract would have to implement a method that updates the addresses in the list. With Scrypto, we have a better way.

Introducing badges

The Radix Engine’s asset-oriented approach solves this issue with the notion of badges. Badges are like any other resources that you would define on Radix, with the difference of being able to use them as authorization directly in Scrypto code. Let’s see some examples.

You can define which badges need to be presented in order for a user to be able to call a method by attaching the authorization rules to the component just before globalizing it:

struct BlueprintWithAuthorization {
    admin_badge_def: ResourceAddress
}

impl BlueprintWithAuthorization {
    pub fn instantiate_component() -> (ComponentAddress, Bucket) {
        // Create the admin badges
        let badges: Bucket = ResourceBuilder::new_fungible()
            .divisibility(DIVISIBILITY_NONE)
            .metadata("name", "Admin Badge")
            .initial_supply(2);

        let component = Self {
            admin_badge_def: badges.resource_address()
        }.instantiate();

        // Define the access rules for this blueprint.
        let access_rules = AccessRules::new()
            .method("do_admin_task", rule!(require(badges.resource_address())));

        // Return the component and the badges
        (component.add_access_check(access_rules).globalize(), badges)
    }

    pub fn do_admin_task(&self) {
        // This method can only be called if the user
        // presents a badge of the `admin_badge_def` resource definition
    }
}

In the instantiator function, we create a badge that the user will need to present in order to call the do_admin_task method. Notice how we return the two badges instead of directly sending them to the accounts. We do this to make the blueprint more composable. At the transaction layer, the caller will specify who will receive the badges. This is like at the movie theater. After buying the tickets, the cashier put them on the counter instead of inserting them directly in your pocket.

Badge as identification

Also, if your blueprint needs to identify a user, instead of using their address give them a badge and use that badge’s address as identification:

#[derive(NonFungibleData)]
struct MemberData {
    name: String
}

blueprint! {
    struct Counter {
        count: HashMap<NonFungibleId, u128>,
        identification_minter: Vault,
        identification_nft_address: ResourceAddress
    }

    impl Counter {
        pub fn instantiate_counter() -> ComponentAddress {
            // Badge kept by the component to mint identification NFTs
            let identification_minter: Bucket = ResourceBuilder::new_fungible()
                .divisibility(DIVISIBILITY_NONE)
                .initial_supply(1);

            // NFT used to identify users
            let identification_nft_address: ResourceAddress = ResourceBuilder::new_non_fungible()
                .mintable(rule!(require(identification_minter.resource_address())), LOCKED)
                .no_initial_supply();

            // Instantiate the Counter component
            Self {
                count: HashMap::new(),
                identification_minter: Vault::with_bucket(identification_minter),
                identification_nft_address: identification_nft_address
            }.instantiate().globalize()
        }

        pub fn become_member(&mut self, name: String) -> Bucket {
            // Mint a new non fungible badge
            let badge = self.identification_minter.authorize(|| {
                let identification_nft_manager: &ResourceManager = borrow_resource_manager!(self.identification_nft_address);
                identification_nft_manager.mint_non_fungible(&NonFungibleId::random(), MemberData{ name: name })
            });

            // Set its counter to 0
            self.count.insert(badge.non_fungible::<MemberData>().id(), 0);

            badge
        }

        pub fn increase_counter(&mut self, identification_badge: Proof) {
            let identification_id = identification_badge.non_fungible::<MemberData>().id();

            assert!(self.count.contains_key(&identification_id), "Invalid badge provided");
            self.count.entry(identification_id).and_modify(|old_count| { *old_count += 1 });
        }
    }
}

In this example, users who call the become_member method receive a non fungible badge. They can then present a proof of the badge to the increase_counter method to increase the count associated with that badge. This example shows that you don’t have to use fungible resources to represent badges. If you need to attach information to your users, you can use non fungible tokens and put their information in the metadata.