💡 The Value Proposition: When you tokenize a real-world asset (a building, a treasury bond, a batch of gold), you can't rely on "whoever holds the private key controls it." Regulated assets require strict, auditable, programmable access control. This post shows how we implement institutional-grade RBAC (Role-Based Access Control) on Solana using PDAs — with 42 comprehensive security tests.
Built with: Rust · Anchor Framework · PDAs · 42 Security Tests — View the test suite
🎯 The Problem: Centralization vs. Security in Tokenized Assets
In a standard ERC-20 or SPL token, whoever holds the private key that can sign transactions has full control. This works for Bitcoin or meme coins. But for regulated assets — real estate, treasury bills, private credit — this model is unacceptable:
| Standard Token | RWA Token |
|---|---|
| Anyone can mint (if they have admin key) | Only authorized agents can mint |
| No freeze mechanism | Regulators can freeze suspicious accounts |
| Single authority model | Separated roles (Owner, Agent, Freeze, Holder) |
| No audit trail of permissions | Every permission change is on-chain |
| "Code is law" | "Code enforces regulation" |
"Security in RWAs should not depend on trust in a person, but on the mathematical verification of the code."
In our Solana RWA platform, we don't rely on a single centralized authority. Instead, we implement a system of Role-Based Access Control (RBAC) and an architecture of Authorized Agents.
🧠 The Power Hierarchy: Four Roles, Zero Single Points of Failure
To prevent any single point of failure from compromising the asset's integrity, we separate responsibilities into four distinct roles:
flowchart TD
Owner["👑 Owner<br/>(Legal Entity)<br/>Add agents, transfer ownership"]
Owner --> FreezeAuth["🔒 Freeze Authority<br/>(Compliance Officer)<br/>Freeze/unfreeze accounts"]
Owner --> Agent["🔑 Agent<br/>(Professional Custodian)<br/>Mint/burn tokens"]
Agent --> Holder["👤 Holder<br/>(End User/Investor)<br/>Transfer tokens"]
Owner -.->|Delegates| Agent
Owner -.->|Delegates| FreezeAuth
style Owner fill:#ffe66d
style FreezeAuth fill:#ff6b6b
style Agent fill:#4ecdc4
style Holder fill:#a8e6cf| Role | Primary Responsibility | Key Permissions |
|---|---|---|
| 👑 Owner | Global Administration | Add agents, transfer token ownership |
| 🔒 Freeze Authority | Security & Compliance | Freeze and unfreeze accounts |
| 🔑 Agent | Supply Management | Execute mint and burn operations |
| 👤 Holder | End User | Transfer tokens (if account not frozen) |
Why This Separation Matters for Institutions
Real-world analogy: The Owner is the legal entity owning the building. The Agent is a professional property manager. The Freeze Authority is a court-appointed compliance officer. The Holder is the investor who owns a tokenized share.
This separation is vital for institutional adoption. No single person controls everything. If an Agent's keys are compromised, the attacker can mint tokens but cannot freeze accounts or transfer ownership.
🛠️ Technical Deep Dive: PDAs as the Authorization Layer
How do we guarantee at the code level that an agent is truly who they claim to be? The answer lies in PDAs (Program Derived Addresses).
The Agent Registry (AgentEntry)
Instead of storing a list of agents within the TokenState (which would limit the number of agents and increase compute costs), we use a separate PDA account for each agent:
Anchor Access Control Example
Here's how the role-based access control looks using Anchor's macros:
This Anchor code demonstrates how access control is enforced at the program level — not by trusting individuals, but by verifying cryptographic signatures against PDA-based role definitions.
The Authorization Flow
This means that for every combination of Token + Wallet, there is a unique on-chain account that acts as a digital "ID card."
flowchart TD
Token["🪙 Token Mint<br/>(e.g., RWA_TOKEN)"]
Token --> Agent1["🔑 Agent 1 PDA<br/>Seed: agent+token+key1<br/>Status: ACTIVE"]
Token --> Agent2["🔑 Agent 2 PDA<br/>Seed: agent+token+key2<br/>Status: ACTIVE"]
Token --> Agent3["🔑 Agent 3 PDA<br/>Seed: agent+token+key3<br/>Status: RESIGNED"]
Agent1 --> Mint1["✅ Can mint/burn"]
Agent2 --> Mint2["✅ Can mint/burn"]
Agent3 --> NoMint["❌ Cannot operate<br/>Account closed"]
style Agent1 fill:#4ecdc4
style Agent2 fill:#4ecdc4
style Agent3 fill:#ff6b6b
style NoMint fill:#ff6b6bThe Authorization Flow
Agent management follows a strictly controlled lifecycle:
sequenceDiagram
participant Owner as Owner Wallet
participant Program as RWA Program
participant PDA as AgentEntry PDA
participant Agent as Agent Wallet
Owner->>Program: add_agent(agent_pubkey)
Program->>Program: Verify Owner signature
Program->>PDA: Create PDA account
PDA-->>Program: AgentEntry created
Program-->>Owner: ✅ Agent authorized
Agent->>Program: mint(amount, destination)
Program->>PDA: Load AgentEntry PDA
PDA-->>Program: AgentEntry found
Program->>Program: Verify Agent signature
Program->>Program: Execute mint
Program-->>Agent: ✅ Tokens minted
alt Agent not authorized
Program->>PDA: Try to load AgentEntry
PDA-->>Program: PDA not found
Program-->>Agent: ❌ Unauthorized error
endAgent Lifecycle
stateDiagram-v2
[*] --> Unregistered: Agent doesn't exist
Unregistered --> Active: Owner calls add_agent()
Active --> Active: Agent performs mint/burn
Active --> Resigned: Agent calls remove_agent()
Active --> Frozen: Freeze Authority freezes agent's users
Resigned --> [*]: PDA account closed, SOL refunded
note right of Active
AgentEntry PDA exists
Can mint and burn
On-chain audit trail
end note
note right of Resigned
Agent voluntarily leaves
Rent (SOL) recovered
Cannot operate anymore
end note🛡️ The Freeze Mechanism: Compliance by Code
Movement control doesn't end with agents. To comply with AML/KYC regulations, the system includes a Freeze Authority.
How Freezing Works
Through the FrozenEntry PDA, the system can mark an account as "frozen":
flowchart LR
Normal["👤 Normal Account<br/>✅ Can transfer"]
FreezeRequest["🔒 Freeze Request<br/>(Freeze Authority)"] --> Frozen["🚫 Frozen Account<br/>❌ Cannot transfer<br/>❌ Cannot receive"]
Frozen -->解冻Request["🔓 Unfreeze Request<br/>(Freeze Authority)"] --> Normal
style Normal fill:#4ecdc4
style Frozen fill:#ff6b6bKey insight: Any attempt to
transferfrom a frozen account is rejected by the program, regardless of whether the user has sufficient balance. This allows regulators to stop funds in cases of suspected fraud without affecting the rest of the ecosystem.
📊 Rigorous Verification: 42 Security Tests
A security implementation is irrelevant if it hasn't been put to the test. In our solana-rwa repository, we have implemented an exhaustive security suite (cases SC-001 to SC-042).
Critical Test Scenarios
flowchart TD
subgraph "Authentication Tests"
Test1["SC-001: Unauthorized Mint<br/>Verify non-agent cannot mint"]
Test2["SC-007: Signature Attacks<br/>Only Owner can add agents"]
Test3["SC-015: PDA Validation<br/>Invalid PDA rejected"]
end
subgraph "Freeze Tests"
Test4["SC-020: Freeze Account<br/>Authority can freeze"]
Test5["SC-025: Transfer from Frozen<br/>Frozen account cannot transfer"]
Test6["SC-030: Agent Cannot Override<br/>Agent can't unfreeze"]
end
subgraph "Lifecycle Tests"
Test7["SC-035: Remove Agent<br/>Agent can resign"]
Test8["SC-038: Rent Recovery<br/>SOL refunded on resignation"]
Test9["SC-042: Ownership Transfer<br/>New Owner inherits all roles"]
end
Test1 --> AllPass["✅ All 42 tests pass"]
Test2 --> AllPass
Test3 --> AllPass
Test4 --> AllPass
Test5 --> AllPass
Test6 --> AllPass
Test7 --> AllPass
Test8 --> AllPass
Test9 --> AllPass
style AllPass fill:#4ecdc4Test Categories
| Category | Tests | What We Verify |
|---|---|---|
| Authentication | SC-001 to SC-015 | Only authorized PDAs can perform operations |
| Freeze Mechanism | SC-020 to SC-030 | Frozen accounts cannot transfer, even by agents |
| Lifecycle | SC-035 to SC-042 | Agent resignation, rent recovery, ownership transfer |
| Edge Cases | SC-043 to SC-042 | Overflow, underflow, zero-amount operations |
📈 Impact: What Programmable Security Enables
| Feature | Traditional Asset Management | RWA with RBAC |
|---|---|---|
| Access Control | Legal contracts, offline | On-chain, programmable, auditable |
| Compliance | Manual, periodic | Real-time, automatic |
| Audit Trail | Paper records, delays | Every action on-chain |
| Single Point of Failure | Key holder has all power | Separated roles, no single control |
| Regulatory Response | Days to freeze assets | Instant on-chain freeze |
| Trust Model | Trust people | Trust code + people |
🔗 Why This Matters Beyond RWAs
The RBAC + PDA pattern demonstrated here applies to any system that requires role-based access control on blockchain:
- DAO governance — who can execute proposals
- Multi-sig wallets — which signatures are required
- DeFi protocols — who can pause, upgrade, or manage treasury
- Gaming platforms — who can mint in-game assets
The progression is natural: First, you learn what RWAs are and why performance matters (Intro). Then, you learn how to secure them (this post). Finally, you see the complete picture of the RWA revolution (Conclusion).
✅ Key Takeaways
- RBAC is essential for RWAs — regulated assets require separated roles, not single-point control
- Four roles: Owner, Freeze Authority, Agent, Holder — each with distinct permissions
- PDAs as AgentEntry — separate account per agent, seed:
[b"agent", token, agent_pubkey] - Agent lifecycle: add → operate → resign — agents can voluntarily leave and recover rent
- Freeze Authority for compliance — AML/KYC compliance via on-chain account freezing
- 42 security tests (SC-001 to SC-042) — comprehensive verification of all security assumptions
- Code enforces regulation — mathematical verification replaces trust in individuals
🔗 Explore the Implementation
The security logic and comprehensive test suite are available in:
| Resource | Description | Link |
|---|---|---|
| Main Repository | Complete RWA platform | github.com/87maxi/rwa |
| Security Tests | All 42 security test cases | rwa/tests/security |
| Agent Logic | PDA-based agent registry implementation | rwa/programs/agent |
| Freeze Mechanism | FreezeAuthority PDA and logic | rwa/programs/freeze |
| RWA Series Intro | Why performance matters for RWAs | rwa-intro-latency-costs |
| RWA Conclusion | The complete RWA revolution picture | rwa-conclusion-high-performance |