Introduction
Radix enforces a strict invariant: every resource must exist inside a container at all times. There are two container types: Vaults (permanent, on-ledger storage) and Buckets (temporary, transaction-scoped carriers). The Radix Engine guarantees that resources cannot be duplicated, accidentally destroyed, or left in limbo — if your transaction ends with an undeposited bucket, it fails. This "physical resource" model eliminates entire categories of bugs common in other smart contract platforms.
Vault Patterns
Single-Vault Component
The simplest pattern: a component holds one vault to store a single resource type. A token sale component, for example, stores tokens in a vault and returns XRD payment to the buyer's account.
struct TokenSale {
token_vault: Vault, // holds tokens for sale
xrd_vault: Vault, // collects XRD payments
price: Decimal,
}Multi-Vault Component (HashMap)
When a component needs to manage arbitrary resource types (e.g. a DEX liquidity pool or a wallet-like component), use a HashMap<ResourceAddress, Vault>. When a new resource type is deposited, create a new vault; when an existing type arrives, deposit into the matching vault.
struct MultiVault {
vaults: HashMap<ResourceAddress, Vault>,
}
impl MultiVault {
pub fn deposit(&mut self, bucket: Bucket) {
let addr = bucket.resource_address();
self.vaults
.entry(addr)
.or_insert_with(|| Vault::new(addr))
.put(bucket);
}
}Vault Take & Put
Withdraw resources from a vault with .take(amount) (returns a Bucket) or .take_all(). Deposit with .put(bucket). For non-fungibles, use .take_non_fungible(&id) or .take_non_fungibles(&ids) to withdraw specific items.
Bucket Patterns
Bucket Passing (Move Semantics)
Buckets use Rust's ownership model — passing a bucket to a function moves it. The caller no longer has access. This prevents double-spending at the language level:
// Caller creates a bucket, passes it to the component
let payment: Bucket = account.withdraw(xrd_address, dec!("100"));
let tokens: Bucket = sale_component.buy(payment);
// 'payment' is now consumed — cannot be used again
account.deposit(tokens);Bucket Splitting
Use .take(amount) on a bucket to split it into two buckets — the original retains the remainder. This is how you implement partial fills, fee extraction, or distributing resources across multiple destinations within a single transaction.
The Worktop
In transaction manifests, returned resources land on the worktop — a temporary staging area. Use TAKE_FROM_WORKTOP to create named buckets from worktop resources, then pass them to subsequent method calls. The transaction fails if any resources remain on the worktop at the end.
