---
title: "10. Badge Patterns: User, Actor and Transient Badges"
url: "https://radix.wiki/developers/scrypto/10-badge-patterns"
updated: 2026-11-14
license: CC-BY-4.0
license_url: "https://creativecommons.org/licenses/by/4.0/"
version: "1.0.0"
---

# 10. Badge Patterns: User, Actor and Transient Badges

|  |  |
| --- | --- |
| Series | [Scrypto track](/developers/scrypto), part 10 |
| Tested with | Scrypto 1.4.0 and `scrypto-test`'s ledger simulator, 14 November 2026 |
| Example | [Basic Flash Loan](https://github.com/radixdlt/scrypto-examples/tree/main/defi/basic-flash-loan) |

## Introduction

A [badge](/contents/tech/core-concepts/badges) is an ordinary [resource](/contents/tech/core-concepts/resources) that a component checks instead of checking who is calling. The Radix docs describe three ways of using one, for three different callers: a **user badge** proves a person's authority, an **actor virtual badge** proves a component's authority, and a **transient badge** proves that a caller has not yet finished what it started ([User Badge Pattern](https://docs.radixdlt.com/docs/user-badge-pattern), [Actor Virtual Badge Pattern](https://docs.radixdlt.com/docs/actor-virtual-badge-pattern), [Transient Badge Pattern](https://docs.radixdlt.com/docs/transient-badge-pattern)).

Every example here was compiled against Scrypto 1.4.0 and run in the ledger simulator. Two changes from the docs' versions were needed. The docs store a new resource in a field typed `ResourceManager`; in 1.4.0 `create_with_no_initial_supply` returns a typed `FungibleResourceManager` or `NonFungibleResourceManager`, and the untyped field is a type error (E0308). The docs' flash loan also calls `mint_uuid_non_fungible`, which does not exist; the method is `mint_ruid_non_fungible`.

## Why use a badge instead of the caller's address?

On Ethereum a contract usually checks `msg.sender` against a stored owner or whitelist. The docs list why that fits Radix badly: one key can control many Radix accounts, so authorising one account and not its siblings is arbitrary; the contract needs extra methods to add, remove or replace addresses, and a mistake in them can lock the owner out for good; and the owner can never move to a new account, even after a hack ([User Badge Pattern](https://docs.radixdlt.com/docs/user-badge-pattern)).

A badge moves authority from the address to an asset. Whoever can produce a proof of the badge holds the authority, and the badge can be moved, held by several accounts, required in multiples, or held in an [access controller](/contents/tech/core-concepts/access-controller) with recovery rules. [Authorization and Access Rules](/developers/scrypto/03-authorization-and-badges) covers the admin and user badge in full. The rest of this guide covers the two patterns for callers that are not people.

## How does a component prove its own authority?

A component often needs a privileged action of its own, such as minting a user badge each time someone signs up. It does not need a badge in a vault for this. Every global component has an **actor virtual badge**: a proof the engine adds automatically when that component makes a call. The `global_caller(address)` rule requires that proof, so a resource can name one component as its only minter ([Actor Virtual Badge Pattern](https://docs.radixdlt.com/docs/actor-virtual-badge-pattern)):

```
use scrypto::prelude::*;

#[blueprint]
mod rad_social {
    struct RadSocial {
        user_badge_manager: FungibleResourceManager,
    }

    impl RadSocial {
        pub fn instantiate_rad_social() -> Global {
            // Reserve the component's address before the component exists.
            let (address_reservation, component_address) =
                Runtime::allocate_component_address(RadSocial::blueprint_id());

            let user_badge = ResourceBuilder::new_fungible(OwnerRole::None)
                .metadata(metadata!(init { "name" => "User Badge", locked; }))
                .mint_roles(mint_roles! {
                    minter => rule!(require(global_caller(component_address)));
                    minter_updater => rule!(deny_all);
                })
                .create_with_no_initial_supply();

            Self { user_badge_manager: user_badge }
                .instantiate()
                .prepare_to_globalize(OwnerRole::None)
                .with_address(address_reservation) // use the reserved address
                .globalize()
        }

        pub fn create_user(&mut self) -> FungibleBucket {
            self.user_badge_manager.mint(1)
        }
    }
}
```

The rule needs the component's address before the component exists. `Runtime::allocate_component_address` reserves an address and returns it with a reservation, and `.with_address(reservation)` globalizes the component at that address.

In the ledger simulator, `create_user` minted a badge into the caller's account. A manifest that called `MINT_FUNGIBLE` on the same resource directly failed with `AuthError(Unauthorized)`: the `minter` role requires a non-fungible from the engine's global-caller badge resource, whose ID is derived from the component's address, and no manifest can present it.

A second virtual badge works at package level. `package_of_direct_caller(package_address)` is satisfied by any blueprint in that package when it makes the call directly, which suits a resource that several blueprints in one package should be able to mint ([proof_rule.rs](https://github.com/radixdlt/radixdlt-scrypto/blob/main/radix-engine-interface/src/blueprints/resource/proof_rule.rs)).

## How do I make sure a caller calls a second method before the transaction ends?

Give them a **transient badge**: a resource no vault can accept. The first method mints it and returns it with whatever it hands out; the second method takes it back and burns it. Because nothing can hold it, the only way for the transaction to commit is to pass it to the second method ([Transient Badge Pattern](https://docs.radixdlt.com/docs/transient-badge-pattern)).

The docs' example is a flash loan: a loan taken and repaid within one transaction, with no collateral, which works only if repayment is guaranteed. The badge is a non-fungible whose data records the amount due:

```
#[derive(NonFungibleData, ScryptoSbor)]
pub struct LoanDue {
    pub amount_due: Decimal,
}

#[blueprint]
mod basic_flash_loan {
    struct BasicFlashLoan {
        loan_vault: Vault,
        loan_terms: NonFungibleResourceManager,
    }

    impl BasicFlashLoan {
        pub fn instantiate(liquidity: Bucket) -> Global {
            let (address_reservation, component_address) =
                Runtime::allocate_component_address(BasicFlashLoan::blueprint_id());

            let loan_terms = ResourceBuilder::new_ruid_non_fungible::(OwnerRole::None)
                .metadata(metadata!(init { "name" => "Flash loan terms", locked; }))
                .mint_roles(mint_roles! {
                    minter => rule!(require(global_caller(component_address)));
                    minter_updater => rule!(deny_all);
                })
                .burn_roles(burn_roles! {
                    burner => rule!(require(global_caller(component_address)));
                    burner_updater => rule!(deny_all);
                })
                // Transient: no vault anywhere can accept it.
                .deposit_roles(deposit_roles! {
                    depositor => rule!(deny_all);
                    depositor_updater => rule!(deny_all);
                })
                .create_with_no_initial_supply();

            Self { loan_vault: Vault::with_bucket(liquidity), loan_terms }
                .instantiate()
                .prepare_to_globalize(OwnerRole::None)
                .with_address(address_reservation)
                .globalize()
        }

        pub fn take_loan(&mut self, amount: Decimal) -> (Bucket, NonFungibleBucket) {
            assert!(amount = due.amount_due, "Insufficient repayment");
            self.loan_vault.put(repayment);
            terms.burn();
        }
    }
}
```

The `deposit_roles` rule `deny_all`, with its updater also `deny_all`, is what makes the resource transient. The minter and burner roles name the component's own actor virtual badge, so only the lender can create or destroy the terms. `repay_loan` checks the resource of both buckets: the docs' version checks the terms but not the repayment, and relies on `Vault::put` rejecting a bucket of the wrong resource. Checking explicitly makes the failure state its reason.

A borrower's manifest takes the loan, uses it, and repays it:

```
CALL_METHOD Address("") "take_loan" Decimal("100");
# ...use the borrowed tokens on the worktop here: a swap, a liquidation...
CALL_METHOD Address("") "withdraw" Address("") Decimal("0.1");
TAKE_ALL_FROM_WORKTOP Address("") Bucket("repayment");
TAKE_ALL_FROM_WORKTOP Address("") Bucket("terms");
CALL_METHOD Address("") "repay_loan" Bucket("repayment") Bucket("terms");
```

All four ways of running it were tested. Borrowing 100 $XRD and repaying 100.1 committed. Depositing the loan and the terms into the borrower's account failed with `Unauthorized` on the `depositor` role: the account owner's signature lets a deposit past the account's own rules, but not past the resource's. Depositing only the $XRD and leaving the terms on the worktop failed with `DropNonEmptyBucket` when the transaction ended. Repaying 100 failed with the panic `Insufficient repayment`. In each failure the whole transaction rolled back, the loan included.

The pattern generalises to any sequence: each method burns the badge the previous method minted and mints one only the next method can burn, and the transaction commits only if it reaches the last one.

## Next Steps

- [Deposit and Withdraw Patterns](/developers/scrypto/11-deposit-and-withdraw-patterns) – how a component pays people who are not in the transaction
- [Testing Scrypto Blueprints](/developers/scrypto/05-testing-scrypto) – the ledger simulator used for every result on this page

## External Links

- [User Badge Pattern](https://docs.radixdlt.com/docs/user-badge-pattern) – Radix documentation
- [Actor Virtual Badge Pattern](https://docs.radixdlt.com/docs/actor-virtual-badge-pattern) – Radix documentation
- [Transient Badge Pattern](https://docs.radixdlt.com/docs/transient-badge-pattern) – Radix documentation
- [Basic Flash Loan](https://github.com/radixdlt/scrypto-examples/tree/main/defi/basic-flash-loan) – scrypto-examples repository
