Non-EVM Standards

Solana RWA Token Program

Learn how the Solana RWA token program enables compliant real-world asset tokenization with on-chain controls and extensibility.

Last updated: 12/29/2025
Improve this page

The RWA Token Program, a Solana-based suite of smart contracts developed by Upside, extends SPL Token-2022 to provide a unified framework for permissioned security tokens representing claims on underlying real-world assets. For Real World Asset (RWA) protocols, this program is especially valuable, as tokens can express fractional ownership in tokenized Treasuries, real estate, equities, debt instruments, or other off-chain collateral while maintaining a compliant, interoperable interface for minting, transfers, vesting, and distributions without fragmented custom logic.

Before the RWA Token Program, Solana RWA projects built ad-hoc compliance layers atop Token-2022, leading to inconsistent transfer rules, siloed vesting mechanics, and elevated audit overheads. The RWA Token Program resolved this by introducing a modular suite, Access Control, Transfer Restrictions, Tokenlock, and Dividends, that leverages Token-2022's transfer hooks for on-chain enforcement. For RWA builders, the program offers a robust abstraction layer supporting off-chain KYC/AML integration, oracle-fed holder limits, and asynchronous vesting releases, while enabling seamless composability with Solana DeFi protocols like Jupiter or Marginfi.

A key design principle is hook-driven modularity, where the core Token-2022 token exposes standardized SPL interfaces augmented by specialized programs without exposing internal role logic. This is well suited to RWA tokens where transfers may depend on off-chain regulatory data or time-locked schedules, yet integrators like wallets or aggregators still need reliable, auditable flows.
 

Use of RWA Token Program in RWA Contexts

The RWA Token Program is the foundational suite for permissioned RWA tokens on Solana because it abstracts the complexity of compliant tokenization and management into an extensible, Token-2022-compatible framework. While traditional DeFi tokens use basic SPL for unrestricted transfers, RWA tokens adapt the model to regulated, illiquid assets, tokenized private credit, commercial real estate, equities, or bond portfolios, where on-chain tokens represent legally backed off-chain value held by custodians or SPVs, with built-in restrictions for securities laws like Reg D or MiCA.
 

Key Applications in RWA Development

Fractional Ownership and Liquidity Provision

Many RWAs are high-value and indivisible. The RWA Token Program allows developers to mint fungible tokens proportional to deposits, unlocking fractional ownership and secondary-market liquidity under compliance rules.

  • Implement mint via Reserve Admin with holder limits to cap concentrations.
  • Tokens can trade freely within allowed groups on DEXs, offering liquidity for assets that would otherwise be locked for years.
  • Example: An equity token where transfer rules restrict to accredited investors, with holder caps enforced on-chain.
     

Yield Accrual and NAV Management

The dividends program automates profit distribution (e.g., interest from debt tokens), with snapshots querying oracles (e.g., Pyth) for historical holdings. This supports merkle-based claiming without manual interventions, ideal for tokenized funds or securities.

Using trusted sources (Pyth, Switchboard) ensures accurate snapshots, critical for compliance and for investor claims. Regulators increasingly expect transparent distribution reporting, and the program provides this without exposing internal merkle state.
 

Redemption and Settlement Mechanics

RWA redemptions are inherently asynchronous, as physical settlement layers (banks, custodians, transfer agents) cannot finalize instantly.

Patterns include:

  • burn with Tokenlock for queued releases.
  • Transfer hook validations for async flows.
  • Memo-required events for signed claims.
  • Proof-of-reserve checks before fulfillment.

These guardrails prevent unauthorized burns and ensure token solvency, especially for semi-liquid assets like private credit or T-bills.
 

Composability Across the RWA Stack

RWA Token Program tokens plug directly into Solana DeFi:

  • Marginfi accepts tokens as collateral within group restrictions.
  • Jupiter pairs can provide deep secondary liquidity for permitted transfers.
  • Aggregators (e.g., Kamino) route into the program natively.

For regulated RWAs:

  • Pair with transfer restrictions for investor eligibility gating.
  • Combine with Tokenlock for modular vesting.
  • Integrate identity proofs via access control roles.

This drastically reduces integration complexity, for example, a tokenized debt token can list across lending markets without custom adapters.
 

Developer Advantages in RWA Builds

  • Regulatory Alignment: The hook-enforced restrictions hide off-chain custodian processes while preserving accurate validations for MiFID II/MiCA-aligned disclosures.
  • Extensibility: Transfer hooks and role bitmasks allow injecting RWA-specific logic i.e., pricing oracles, geofencing, or IPFS-pinned compliance policies.
  • Risk Isolation: Tokens remain fully SPL compliant, freezes do not impact wallet-held balances outside rules.
  • Upgrade Path: Existing Token-2022 tokens can migrate via wrappers, preserving TVL and simplifying audits.

To visualize the end-to-end flow in an RWA token, the following sequence diagram captures the typical mint - transfer - burn cycle. It illustrates how users interact with the token mint, how programs interact with hooks and oracles, and how off-chain compliance updates flow back on-chain through validations or snapshots. This representation is especially important for understanding asynchronous transfers, delayed settlements, and rule-driven compliance in RWA systems.

solana RWA.svg

This diagram highlights how the RWA Token Program acts as the connective tissue between on-chain token operations and off-chain asset realities. Mints enforce limits proportional to contributed value, hooks capture real-world compliance as transfers execute, and merkle functions help manage distributions during burns. For illiquid RWA portfolios, the model can be extended with explicit group rules between the mint (M) and the custodian or SPV (C) to handle delayed settlement cycles.
 

Core Specification

An RWA Token Program mint must implement the core SPL Token-2022 interface for its fungible structure, the permissioned representation of a user’s proportional claim on the underlying asset. Optional integrations with the suite's programs enable advanced features like role-based controls, a valuable enhancement for RWA use cases where institutions manage access without needing full custom programs.

In the RWA Token Program, the mint behaves like any Token-2022 token (balance queries, total supply, transfers), while the suite's programs refer to appended logic for collateral management via hooks. The specification enforces a clean separation between base tokens and restriction modules, ensuring predictable operations and seamless integration across Solana and RWA systems.
 

Key Definitions

  • Mint: The Token-2022 configuration with hooked extension data, e.g., a tokenized equity or debt instrument.
  • Account: The token balance holder, extensible for group assignments or locks.
  • Total Supply: The aggregate token value managed by the mint, equal to the sum of principal, vested amounts, and adjustments for fees or losses.
  • Restriction Modules: (e.g., Transfer Hook, Access Control). Provide idealized, rule-agnostic behaviors for UI, analytics, and Oracle integrations. They represent the regulatory relationship between base tokens and enforced logic.
  • Hook Functions: (e.g., execute transfer) Provide near-exact estimates of expected outcomes, reflecting rules, rounding, or NAV changes, without executing state updates. These enable accurate transaction planning.
  • Rounding Rules: The RWA Token Program mandates rounding in favor of the mint:
    • Round down when issuing tokens or deductions, preventing dilution.
    • Round up when applying caps or requirements, protecting existing participants. These rules ensure fair distribution and discourage precision-based attacks.

The RWA Token Program defines four interconnected programs with 20+ instructions, organized by their roles in access, restrictions, vesting, and distributions. Every program integrates at deployment, ensuring a single source of truth for token behavior and movements. Below is the Rust-equivalent interface, accompanied by developer notes covering edge cases, rounding behavior, and considerations specific to RWA tokens such as asynchronous hooks and oracle-driven updates.

1use anchor_lang::prelude::*;
2use anchor_spl::token_2022::{self, Token2022};
3
4declare_id!("6yEnqdEjX3zBBDkzhwTRGJwv1jRaN4QE4gywmgdcfPBZ"); // Transfer Restrictions example
5
6#[program]
7pub mod rwa_token_program {
8    use super::*;
9
10    // Access Control: Role Management
11    pub fn initialize_access_control(
12        ctx: Context<InitializeAccessControl>,
13        max_supply: u64,
14    ) -> Result<()> {
15        // Set bitmask roles
16        Ok(())
17    }
18
19    pub fn grant_role(ctx: Context<GrantRole>, role: u8, target: Pubkey) -> Result<()> {
20        // Bitmask update
21        Ok(())
22    }
23
24    // Transfer Restrictions: Hook Integration
25    pub fn initialize_transfer_restriction(
26        ctx: Context<InitializeTransferRestriction>,
27        groups: Vec<u8>,
28    ) -> Result<()> {
29        // Setup ExtraAccountMetaList for hook
30        Ok(())
31    }
32
33    pub fn initialize_holder_group(
34        ctx: Context<InitializeHolderGroup>,
35        group_id: u8,
36        holder_id: Pubkey,
37    ) -> Result<()> {
38        // Assign wallet to group
39        Ok(())
40    }
41
42    pub fn initialize_transfer_rule(
43        ctx: Context<InitializeTransferRule>,
44        from_group: u8,
45        to_group: u8,
46        after_timestamp: i64,
47    ) -> Result<()> {
48        // Define rule
49        Ok(())
50    }
51
52    // Tokenlock: Vesting
53    pub fn create_vesting_schedule(
54        ctx: Context<CreateVesting>,
55        cliff: i64,
56        duration: i64,
57        amount: u64,
58    ) -> Result<()> {
59        // Escrow setup
60        Ok(())
61    }
62
63    pub fn release_vested_tokens(ctx: Context<ReleaseVested>) -> Result<()> {
64        // Check schedule, transfer from escrow
65        Ok(())
66    }
67
68    // Dividends: Merkle Distribution
69    pub fn create_distribution(
70        ctx: Context<CreateDistribution>,
71        merkle_root: [u8; 32],
72        token_mint: Pubkey,
73    ) -> Result<()> {
74        // Immutable root set
75        Ok(())
76    }
77
78    pub fn claim_dividend(
79        ctx: Context<ClaimDividend>,
80        proof: Vec<[u8; 32]>,
81        index: u32,
82        amount: u64,
83    ) -> Result<()> {
84        // Verify merkle, transfer
85        Ok(())
86    }
87
88    // Core Transfer with Hook
89    pub fn execute_transfer_hook(
90        ctx: Context<ExecuteTransferHook>,
91        amount: u64,
92    ) -> Result<()> {
93        // Validate rules, groups, limits
94        Ok(())
95    }
96
97    #[event]
98    pub struct TransferEvent {
99        pub amount: u64,
100        pub from: Pubkey,
101        pub to: Pubkey,
102        pub group_from: u8,
103        pub group_to: u8,
104    }
105
106    #[event]
107    pub struct DividendClaimEvent {
108        pub amount: u64,
109        pub holder: Pubkey,
110        pub distribution_id: u32,
111    }
112}

Detailed Function Breakdown (with RWA Developer Notes)

Below is a program-by-program explanation of the RWA Token Program interface, including edge cases, rounding behavior, and RWA-specific implementation guidance for regulated assets, off-chain settlement, and hook-driven validation.
 

initialize_access_control()

Sets up the role-based bitmask (e.g., 1=Contract Admin) with max supply cap. In RWA tokens, this pins the initial governance for tokenized equities, USDC wrappers, or debt notes.

  • This config is upgradable only by Contract Admin to avoid unauthorized changes.
     

grant_role()

Updates bitmask to assign overlapping roles (e.g., multi-sig for Reserve Admin).

  • RWA Integration: For compliance-based assets, tie to oracle-fed KYC (Pyth / Switchboard).
  • Edge Case: Must handle role overlaps or revocations without locking admins.
     

initialize_transfer_restriction()

Appends groups and ExtraAccountMetaList for Token-2022 hook integration.

  • Formula: Groups as u8 enums (e.g., 0=Reg D).
  • Usage: Onboarding dashboards, risk analytics.
  • RWA Note: Reflects pure regulatory mapping, does not account for vesting overrides.
     

initialize_holder_group()

Assigns wallet to holder/group with ID for cap tracking.

  • Ensures no precision-based arbitrage on limits.
  • RWA Note: Useful for accredited investor disclosures in regulated markets.
     

initialize_transfer_rule()

Defines directional rules with timestamps (e.g., Founders to Reg S after T+1Y).

  • Return max for “no limit” on rule count.
  • RWA Note: Enforce jurisdiction caps, temporary pauses due to regulatory flags.
     

create_vesting_schedule()

Escrows tokens with cliff/linear params, bypassing restrictions on release.

  • Discrepancies vs base transfer reveal lock impact.
  • RWA Note: Useful for quote generation in lockup-heavy vaults or those with custody fees.
     

execute_transfer_hook()

Intercepts transfer, validating groups/rules/caps. Emits TransferEvent.

  • Requires prior wallet assignment.
  • Includes compliance hooks for AML evaluation.
  • RWA Note:
    • For illiquid assets (such as real estate and private credit): pair with Tokenlock asynchronous releases.
    • Trigger oracle checks for sanctions/residency validation.
       

create_distribution, claim_dividend

Merkle root setup and proof-based claiming for snapshots.

  • claim(amount) requires exact proof; tokens pulled post-verification.
  • RWA Use Case: Profit sharing, where holders claim fixed amounts based on historical holdings.
     

Reference Implementation

Upside’s RWA Token Program offers a mature, security-audited foundation for building permissioned tokens. The suite integrates with Token-2022, providing a clean extension surface for RWA-specific logic. Its design encourages minimal overrides while still supporting custom rule models, role configs, vesting layers, and oracle integration.

Key capabilities include:

  • Inflation Attack Protection: The implementation prevents dilution by enforcing max supply and admin checks. Early minters cannot exploit low-supply corner cases to extract excess value.
  • Rounding Compliance: All validations use checked arithmetic for precise, deterministic operations that adhere to SPL requirements of rounding in favor of the mint. This eliminates subtle precision-drift exploits.
  • Extensible Hooks: Lifecycle hooks such as execute_transfer allow developers to embed RWA-specific functionality, NAV oracle checks, compliance gating, settlement status validation, or cap accounting, without modifying core logic.
  • Flexible Structure: The programs build on Anchor’s base, with optional multi-sig or access controls for regulated administration. This makes it straightforward to integrate custodial controls, upgrade pathways, or role-gated operations required in regulated RWA environments.
1use anchor_lang::prelude::*;
2use anchor_spl::token_2022::{self, Token2022};
3
4declare_id!("4X79YRjz9KNMhdjdxXg2ZNTS3YnMGYdwJkBHnezMJwr3"); // Access Control example
5
6#[program]
7pub mod rwa_access_control {
8    use super::*;
9
10    pub fn initialize_access_control(
11        ctx: Context<InitializeAccessControl>,
12        max_total_supply: u64,
13    ) -> Result<()> {
14        let config = &mut ctx.accounts.config;
15        config.admin = ctx.accounts.admin.key();
16        config.max_total_supply = max_total_supply;
17        config.roles = 0; // Initial bitmask
18        Ok(())
19    }
20}
21
22#[derive(Accounts)]
23pub struct InitializeAccessControl<'info> {
24    #[account(init, payer = payer, space = 8 + 32 + 8 + 1)]
25    pub config: Account<'info, AccessControlConfig>,
26    #[account(mut)]
27    pub payer: Signer<'info>,
28    pub system_program: Program<'info, System>,
29    // ...
30}
  • The initializer pins roles at creation. For RWA tokens, prefer multi-sig admins and link metadata to IPFS for provenance.
  • Bitmask can be extended for custom roles in UIs.
     

Mint Flow (Admin-Facing)

Admin-level instructions call core logic with restrictions.

1pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
2    // Check Reserve Admin role
3    require!(ctx.accounts.admin.roles & 2 != 0, Error::Unauthorized);
4
5    // Validate supply cap
6    let current_supply = ctx.accounts.mint.supply;
7    require!(current_supply + amount <= ctx.accounts.config.max_total_supply, Error::SupplyExceeded);
8
9    let cpi_accounts = anchor_spl::token_2022::MintTo {
10        mint: ctx.accounts.mint.to_account_info(),
11        to: ctx.accounts.to.to_account_info(),
12        authority: ctx.accounts.mint_authority.to_account_info(),
13    };
14    let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts);
15    anchor_spl::token_2022::mint_to(cpi_ctx, amount)?;
16
17    // Invoke post-mint hook if enabled
18    if let Some(hook) = ctx.accounts.transfer_restrictions.key() {
19        // Custom RWA logic: holder cap check
20    }
21
22    emit!(MintEvent { amount, to: ctx.accounts.to.key() });
23    Ok(())
24}
  • Use CPI for safe minting.
  • Override pre-mint to add compliance checks (KYC, caps) and revert early if onboarding fails.
  • For illiquid or async-backed assets, combine mint with provisional issuance and hook finalization to avoid over-minting.

Transfer Flow

Symmetric to mint, transfer invokes the hook, then updates accounts.

1pub fn execute_transfer_hook(ctx: Context<ExecuteTransferHook>, amount: u64) -> Result<()> {
2    // Pre-hook: Check groups and rules
3    let from_group = ctx.accounts.from_group.group_id;
4    let to_group = ctx.accounts.to_group.group_id;
5    let rule = ctx.accounts.rules.get_rule(from_group, to_group)?;
6    require!(Clock::get()?.unix_timestamp >= rule.after_timestamp, Error::TransferLocked);
7
8    // Holder cap check
9    let from_holder_cap = ctx.accounts.from_holder.current_holdings + amount; // Simplified
10    require!(from_holder_cap <= ctx.accounts.from_group.max_holdings, Error::CapExceeded);
11
12    let cpi_accounts = anchor_spl::token_2022::Transfer {
13        from: ctx.accounts.from.to_account_info(),
14        to: ctx.accounts.to.to_account_info(),
15        authority: ctx.accounts.authority.to_account_info(),
16    };
17    let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts);
18    anchor_spl::token_2022::transfer(cpi_ctx, amount)?;
19
20    // Post-hook: Update holdings
21    ctx.accounts.from_holder.holdings -= amount;
22    ctx.accounts.to_holder.holdings += amount;
23
24    emit!(TransferEvent { amount, from: ctx.accounts.from.key(), to: ctx.accounts.to.key(), group_from: from_group, group_to: to_group });
25    Ok(())
26}
  • Hook-first follows checks-effects-interactions and reduces reentrancy risk.
  • For queued transfers, implement hook to check status and push into a compliance queue.
  • Add transfer rate limits or liquidity buffers to defend against sudden runs.

Vesting Release

Default releases are schedule-based; RWA overrides query timestamps.

1pub fn release_vested_tokens(ctx: Context<ReleaseVested>) -> Result<()> {
2    let vesting = &ctx.accounts.vesting;
3    let now = Clock::get()?.unix_timestamp;
4    let releasable = vesting.compute_releasable(now); // Cliff/linear calc
5    require!(releasable > 0, Error::NothingToRelease);
6
7    // Transfer from escrow, bypass restrictions
8    let cpi_accounts = anchor_spl::token_2022::Transfer {
9        from: ctx.accounts.escrow.to_account_info(),
10        to: ctx.accounts.recipient.to_account_info(),
11        authority: ctx.accounts.vesting_authority.to_account_info(),
12    };
13    let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts);
14    anchor_spl::token_2022::transfer(cpi_ctx, releasable)?;
15
16    // Update vesting state
17    vesting.released += releasable;
18    Ok(())
19}
  • compute_releasable uses timestamp/nonce to prevent replay.
  • Consider a last-valid-proof fallback and staleness policy.
     

Adding Holder Caps

Cap handling keeps execution consistent.

1const MAX_HOLDING_BASIS: u16 = 1000; // 10% = 1000 / 10000
2
3pub fn check_holder_cap(holder: &HolderAccount, delta: u64, total_supply: u64) -> Result<()> {
4    let max_holding = total_supply * u64::from(MAX_HOLDING_BASIS) / 10000;
5    let new_holding = holder.holdings + delta;
6    require!(new_holding <= max_holding, Error::CapExceeded);
7    Ok(())
8}
  • Keep cap accounting transparent: emit CapCheckEvent and track separately.
  • For RWA products, caps may fund compliance overhead, document in on-chain config and IPFS docs.
Solana Token-2022 Guide
Solana sRFC 00020

WE SECURE EVERYTHING YOU BUILD.

From day-zero risk mapping to exchange-ready audits — QuillAudits helps projects grow with confidence. Smart contracts, dApps, infrastructure, compliance — secured end-to-end.

DeFi SecurityplumeUniswap FoundationAethiropt-collectivePolygon SPNBNB Chain Kickstart

Office 104/105 Level 1, Emaar Square, Building 4 Sheikh Mohammed Bin Rashid Boulevard Downtown Dubai, United Arab Emirates P.O box: 416654

[email protected]

All Rights Reserved. © 2026. QuillAudits - LLC

Privacy Policy