Resources are the foundation of Radix. Unlike other blockchains, tokens and NFTs are native types enforced by the system—not code patterns you implement yourself.
The Big Idea
Resources are physical
On Radix, a token isn't a number in a mapping. It's an object that exists, moves, and can only be in one place at a time. The system enforces this—you can't accidentally duplicate or lose tokens.
Resources vs Traditional Tokens
❌ Ethereum (ERC-20) | ✓ Radix (Resources) |
|---|---|
| |
Two Types of Resources
🪙 Fungible
Interchangeable units. One token is the same as another. Can be divided (based on divisibility setting).
Examples: currencies, governance tokens, utility tokens
🎨 Non-Fungible
Each unit is unique with its own ID and data. Cannot be divided—always whole units.
Examples: NFTs, tickets, badges, certificates
Creating Fungible Resources
Basic fungible token
// Minimal fungible token
let tokens: Bucket = ResourceBuilder::new_fungible(OwnerRole::None)
.mint_initial_supply(1000)
.into();Full-featured fungible token
let tokens: Bucket = ResourceBuilder::new_fungible(OwnerRole::None)
// Divisibility: 0 = whole units only, 18 = max precision
.divisibility(DIVISIBILITY_MAXIMUM)
// Metadata describes the token
.metadata(metadata! {
init {
"name" => "My Token", locked;
"symbol" => "MTK", locked;
"description" => "A demonstration token", updatable;
"icon_url" => Url::of("https://example.com/icon.png"), updatable;
}
})
// Mint the initial supply
.mint_initial_supply(1_000_000)
.into();Metadata locks
locked means the value can never change. updatable means someone with the right authority can change it later. Choose carefully—locked metadata builds user trust.
Creating Non-Fungible Resources
Non-fungibles need a data structure to define what each unit contains:
Define the NFT data structure
#[derive(ScryptoSbor, NonFungibleData)]
struct Ticket {
event_name: String,
seat: String,
#[mutable] // This field can be updated
used: bool,
}Create the NFT resource
let ticket: Bucket = ResourceBuilder::new_integer_non_fungible::<Ticket>(OwnerRole::None)
.metadata(metadata! {
init {
"name" => "Concert Ticket", locked;
}
})
.mint_initial_supply([
(
IntegerNonFungibleLocalId::new(1),
Ticket {
event_name: "Radix Launch Party".to_string(),
seat: "A1".to_string(),
used: false,
}
),
(
IntegerNonFungibleLocalId::new(2),
Ticket {
event_name: "Radix Launch Party".to_string(),
seat: "A2".to_string(),
used: false,
}
),
])
.into();Non-Fungible ID Types
ID Type | Builder Method | Use Case |
|---|---|---|
Integer | new_integer_non_fungible | Sequential IDs (1, 2, 3...) |
String | new_string_non_fungible | Meaningful IDs ("ticket-abc") |
Bytes | new_bytes_non_fungible | Hash-based IDs |
RUID | new_ruid_non_fungible | Auto-generated unique IDs |
Different ID types
// Integer ID
IntegerNonFungibleLocalId::new(42)
// String ID
StringNonFungibleLocalId::new("my-unique-nft").unwrap()
// RUID (auto-generated)
// Just use new_ruid_non_fungible and don't specify IDsResource Behaviors
Resources can have special behaviors enabled. These are controlled by roles—who (which badge) is allowed to perform the action.
Behavior | What it allows | Common use |
|---|---|---|
mintable | Create more tokens after initial supply | Inflationary tokens, NFT minting |
burnable | Destroy tokens permanently | Deflationary mechanics, redemptions |
recallable | Take tokens back from any vault | Compliance, revocable access |
freezable | Prevent transfers | Compliance, security freezes |
restrict_deposit | Control who can receive | Soulbound tokens, KYC |
restrict_withdraw | Control who can send | Vesting, lock-ups |
Mintable token example
// Create a badge that will control minting
let minter_badge: Bucket = ResourceBuilder::new_fungible(OwnerRole::None)
.divisibility(DIVISIBILITY_NONE)
.mint_initial_supply(1)
.into();
// Create a token that's mintable by the badge holder
let tokens: Bucket = ResourceBuilder::new_fungible(OwnerRole::None)
.metadata(metadata! {
init {
"name" => "Mintable Token", locked;
}
})
.mint_roles(mint_roles! {
minter => rule!(require(minter_badge.resource_address()));
minter_updater => rule!(deny_all);
})
.mint_initial_supply(1000)
.into();Choose behaviors at creation time
Most behaviors must be configured when the resource is created. You can't make a non-mintable token mintable later. Plan ahead!
Resource Addresses
Every resource type has a unique ResourceAddress. This is how you identify and verify tokens:
// Get the address of tokens in a bucket
let address: ResourceAddress = my_bucket.resource_address();
// Check if a bucket contains the expected resource
assert!(
payment.resource_address() == XRD,
"Payment must be in XRD"
);
// XRD is a built-in constant for the native token
use scrypto::prelude::XRD;🧪 Practice Exercise
Create a "Membership Badge" NFT:
Define a
MembershipDatastruct with:member_name,tier(String), andjoined_epoch(u64)Make the
tierfield mutable (upgradeable)Create a resource that mints 3 membership badges
Use string IDs based on member names
Show solution
Summary
✓ Resources are native objects, not balance mappings
✓ Fungible = interchangeable, Non-fungible = unique
✓ Metadata describes resources (name, symbol, etc.)
✓ Behaviors control what can be done (mint, burn, recall, etc.)
✓ Every resource has a unique ResourceAddress
Connect your wallet to join the discussion.
