---
title: "9. Permissioned and Regulated Assets"
path: "/developers/scrypto/09-permissioned-and-regulated-assets"
version: "1.0.0"
author: "Hydrate"
createdAt: "2026-07-29T11:09:04.720Z"
updatedAt: "2026-07-29T11:09:04.720Z"
---

# 9. Permissioned and Regulated Assets

## Introduction

Some assets are not meant to move freely. A tokenised bond may only be transferable between investors who have passed KYC; a credential must stay in the account it was issued to; a share class may need to be frozen or clawed back under a court order. On most smart-contract platforms this means writing transfer-hook logic inside the token contract and hoping every integration respects it.

On Radix, tokens are not contracts. They are native [resources](/developers/scrypto/02-resources-and-nfts), and the rules governing their movement are declared on the resource itself at creation time and enforced by the [Radix Engine](/contents/tech/core-protocols/radix-engine) — not by the code that happens to be holding them. That makes permissioning both stronger and more constrained than a transfer hook, and the constraints are worth understanding before you design a regulated asset.

This article covers the movement rules available on a resource, the account-side controls that sit opposite them, and the pattern to reach for when the requirement is "only accounts holding a credential may receive this" — which, as the next sections explain, is not something a deposit rule can express on its own.

## Movement Rules on the Resource

Every behaviour of a resource — minting, burning, withdrawing, depositing, recalling, freezing, updating non-fungible data — is a *role* whose [access rule](/contents/tech/core-concepts/access-rules-and-auth-zones) is set when the resource is created. Each role is paired with an `_updater` role that controls whether the first rule can ever be changed. Setting the updater to `deny_all` locks the rule permanently, and [locking is one-way](https://docs.radixdlt.com/docs/resource-behaviors): there is no path back to a mutable rule.

The two roles that govern movement are `withdrawer` and `depositor`:

```
let regulated_token = ResourceBuilder::new_fungible(OwnerRole::Fixed(rule!(require(admin_badge))))
    .metadata(metadata! { init { "name" => "Regulated Token", locked; } })
    .withdraw_roles(withdraw_roles! {
        withdrawer         => rule!(require(transfer_agent_badge));
        withdrawer_updater => rule!(require(admin_badge));
    })
    .deposit_roles(deposit_roles! {
        depositor         => rule!(require(transfer_agent_badge));
        depositor_updater => rule!(require(admin_badge));
    })
    .create_with_no_initial_supply();
```

A resource whose `withdrawer` is `deny_all` can never leave the vault it lands in — this is how **soulbound** tokens are built, and the usual reason to build one is to bind identity or reputation to an account. A resource whose `depositor` is `deny_all` can never be stored at all: it is a **transient** resource that must be burnt before the transaction ends, or the transaction fails with a dangling-resource error. That failure mode is a feature — it is how a blueprint forces a caller to complete a required step within the same transaction.

Between those two extremes sits the useful case: gate the role behind a badge. A withdraw rule of `rule!(require(transfer_agent_badge))` means the tokens only move when that badge is presented as a [proof](/contents/tech/core-concepts/buckets-proofs-and-vaults) in the same transaction — so every transfer necessarily routes through whoever holds the badge.

## Freeze and Recall

Two further roles exist specifically for regulated assets. **Freezing** suspends withdrawals from a particular vault, and **recall** lets an authority pull a resource out of a vault it does not own. Both are declared the same way as the movement roles:

```
.recall_roles(recall_roles! {
    recaller         => rule!(require(admin_badge));
    recaller_updater => rule!(deny_all);
})
.freeze_roles(freeze_roles! {
    freezer         => rule!(require(admin_badge));
    freezer_updater => rule!(deny_all);
})
```

Recall is the sharper of the two: it is documented under the use case of [rental NFTs](https://docs.radixdlt.com/docs/recalling-resources), but it is equally the mechanism behind a clawback on a regulated instrument. It is also unavoidably a centralisation point, and one that is visible on-ledger — a wallet displays a recallable resource as recallable. If your asset does not genuinely need it, leaving `recall_roles` unset is the safer default; if it does, holders can at least see the power exists before they accept the token.

Because these are engine-enforced roles rather than contract logic, they apply wherever the resource goes. A DEX that never heard of your compliance rules still cannot move a frozen token, because the rule lives on the resource, not on the venue.

## Why a Deposit Rule Cannot Check the Recipient

The recurring question from developers building KYC-gated assets is some version of: *can I set the deposit rule so the token only lands in accounts that hold a KYC badge?* Directly, no — and the reason is worth understanding, because it is the same reason Radix authorisation avoids the caller-address bugs common elsewhere.

An access rule is checked against the [authorization zone](https://docs.radixdlt.com/docs/advanced-accessrules) of the running transaction: the set of proofs that have actually been presented. A proof of a badge sitting in someone else's account can only be created by that account, which requires its owner's authorisation. So when a third party pushes tokens to an account, there is no proof of the recipient's credential available for a rule to test. A `depositor` rule can say *who is allowed to perform the deposit*; it cannot say *what the destination must already hold*.

The pattern that does satisfy the requirement inverts the direction of the transfer. Rather than pushing the asset, the issuer holds it in a component and the recipient **claims** it in their own signed transaction — at which point they can present their credential badge, and the rule can check it. This is the [withdraw pattern](https://docs.radixdlt.com/docs/the-withdraw-pattern), and it is the recommended shape for value flowing to users on Radix generally, not just for regulated assets. The official docs put the case bluntly: hard-coding a "send to the recipient's address" call from inside a blueprint forces a stack of assumptions about what the recipient component even is.

The practical design, then, for a token restricted to credentialled holders:

1. $1

2. $1

3. $1

Where a component must also confirm *which* component called it, [caller requirements](https://docs.radixdlt.com/docs/advanced-accessrules) — `rule!(require(global_caller(<COMPONENT_ADDRESS>)))` and `rule!(require(package_of_direct_caller(<PACKAGE_ADDRESS>)))` — provide implicit proofs of the calling actor. The docs flag these as advanced-use-only and advise explicit badge requirements wherever they will do the job, on the grounds that caller-based patterns on other chains have repeatedly been mis-applied into exploits.

## Account-Side Controls

The other half of the picture belongs to the recipient. Radix [Smart Accounts](/contents/tech/core-protocols/smart-accounts) are components, and every account owner configures what unknown third parties may deposit — a default deposit mode plus a per-resource preference map. Turning off deposits of unrecognised resources is what the community calls ["no airdrop mode"](https://docs.radixdlt.com/docs/account-deposit-patterns). The owner can always deposit into their own account by signing; everyone else goes through the account's `try_deposit` methods, which respect those settings.

That leaves three sanctioned ways to get an asset to a user who is not present to sign:

- **Badge and claim** — the recipient holds a badge and claims from your component whenever they choose. The default, and the one that keeps working when the user switches accounts.

- **Account locker** — the asset is sent to a locker the user's wallet surfaces as a pending claim. A close substitute for "send to address", but it does not work for non-transferable tokens.

- **Authorized depositor** — the user adds your dApp's badge to their account's authorised list in a transaction they sign, after which your deposits succeed regardless of their other settings. This is the narrow path for delivering a soulbound credential to one specific account, typically one the user proved with [ROLA](/developers/frontend/03-rola-authentication).

Before a direct deposit, a backend can ask the [Gateway](/developers/infrastructure/02-radix-apis) whether the account's deposit mode or resource preference would reject it, and fall back to a locker or a claim if so.

## Verifying a Credential Off-Ledger

Compliance checks usually also need to happen outside a transaction — a backend deciding whether to show a trading screen, for instance. The [Gateway API](/developers/frontend/02-gateway-sdk) answers the holdings question in one call when you already know the credential's resource address and ID:

```
POST /state/non-fungible/location
{ "resource_address": "resource_rdx1...", "non_fungible_ids": ["#1#"] }
```

The response returns, per ID, the `owning_vault_address`, its `owning_vault_parent_ancestor_address` and `owning_vault_global_ancestor_address`, and an `is_burned` flag — so comparing the global ancestor against the account you are checking settles both ownership and revocation in a single request. Going the other direction (does *this* account hold any of that resource?), `POST /state/entity/details` with `opt_ins: { non_fungible_include_nfids: true }` returns the account's non-fungible resources, with `/state/entity/page/non-fungibles` and `/state/entity/page/non-fungible-vaults/ids` for pagination.

One caution: a Gateway read tells you the state at a ledger version, not at the moment your transaction executes. Treat it as a UX and routing signal — decide whether to offer the action, which deposit pattern to use — and let the on-ledger access rule remain the thing that actually enforces the restriction.

## Wallet Support for Restricted Transfers

There is a live rough edge here worth planning around. A resource whose withdrawal requires a badge proof cannot be moved from the Radix Wallet's built-in transfer screen, because that flow builds a plain withdraw-and-deposit manifest and never creates the proof the rule demands. Today the answer is that your dApp must supply the manifest.

That may change. Two pull requests opened on 23 July 2026 by community developer [genkipool](/ecosystem/genkipool) propose having the wallet auto-attach the required badge: [sargon #452](https://github.com/radixdlt/sargon/pull/452) extends the `PerAssetTransfers` manifest builder to inject a list of `ResourceSpecifier` badges, calling `create_proof_of_amount` for fungibles and `create_proof_of_non_fungibles` for non-fungibles, and [babylon-wallet-android #1446](https://github.com/radixdlt/babylon-wallet-android/pull/1446) builds the wallet feature on top of it. Both were still open as of 29 July 2026, so design for the manifest-supplied path and treat wallet support as a future convenience rather than something to rely on.

## Next Steps

- [Transaction Manifest Language](/developers/transactions/01-manifest-language) — write the manifest that presents the proof and performs the restricted transfer

- [Testing Scrypto Blueprints](/developers/scrypto/05-testing-scrypto) — assert that an unauthorised transfer actually fails

## External Links

- [Resource Behaviors — Official Docs](https://docs.radixdlt.com/docs/resource-behaviors)

- [Advanced Access Rules — Official Docs](https://docs.radixdlt.com/docs/advanced-accessrules)

- [Account Deposit Patterns — Official Docs](https://docs.radixdlt.com/docs/account-deposit-patterns)

- [The Withdraw Pattern — Official Docs](https://docs.radixdlt.com/docs/the-withdraw-pattern)

- [Recalling Resources — Official Docs](https://docs.radixdlt.com/docs/recalling-resources)

- [Radix Gateway API Reference](https://radix-babylon-gateway-api.redoc.ly/)