Authorization PDAs: enforcing who can touch your RWA vault
A practical look at how STRATA uses Authorization PDAs and Anchor account constraints to enforce access rules for institutional RWA vaults.

Every RWA protocol that wants to serve institutional capital eventually has to answer a simple question in code: who is allowed to touch this vault, and under what conditions? For most DeFi systems, the answer is still “any wallet that calls the function”. For an institutional RWA vault, that is a non-starter.
On Solana, Program Derived Addresses (PDAs) give protocols a way to represent authority and policy without private keys. An Authorization PDA is a small account that encodes who is allowed to do what for a short period of time, and the program can refuse to execute any instruction that does not come with a valid authorization attached.
What an Authorization PDA actually represents
In STRATA, an Authorization PDA is the on-chain projection of an off-chain decision. Off-chain, the identity layer verifies verifiable credentials (KYC, jurisdiction, risk acknowledgments, etc.) and checks them against the protocol’s policies. If everything passes, the backend creates a PDA that encodes:
- the wallet that is allowed to act,
- which tranche or vault it can access,
- which operation is permitted (for example, deposit into senior or junior),
- and an expiry timestamp after which the authorization is no longer valid.
The PDA has no private key. It exists only because the program can derive it using its own program ID and a deterministic set of seeds. That is what makes it safe to treat as an authority on-chain.
A sketch of the PDA structure
In Anchor, the Authorization PDA might be modeled roughly like this:
#[account]
pub struct Authorization {
pub wallet: Pubkey,
pub vault: Pubkey,
pub tranche: u8, // 0 = senior, 1 = junior
pub operation: u8, // 0 = deposit, 1 = redeem, ...
pub expires_at: i64, // unix timestamp
pub bump: u8,
}
The account itself is derived from a stable set of seeds. For example:
// Seeds: ["auth", wallet, vault, tranche, operation]
#[derive(Accounts)]
pub struct DepositWithAuth<'info> {
#[account(
mut,
has_one = vault,
)]
pub authorization: Account<'info, Authorization>,
#[account(
seeds = [
b"auth",
authorization.wallet.as_ref(),
authorization.vault.as_ref(),
&[authorization.tranche],
&[authorization.operation],
],
bump = authorization.bump,
)]
/// CHECK: PDA derived and verified by seeds + bump
pub authorization_pda: UncheckedAccount<'info>,
// ... rest of the accounts (vault, user, token accounts, system)
}
This pattern ensures that only PDAs derived from the agreed seeds are accepted, and the Authorization account itself is tied to the expected wallet and vault.
Enforcing authorization inside the instruction
The instruction logic then does the actual enforcement. At a high level, the flow is:
- Check that
authorization.walletmatches the signer. - Check that
authorization.vaultmatches the vault being accessed. - Check that
authorization.trancheandauthorization.operationmatch the instruction being executed. - Check that
authorization.expires_atis in the future. - Only then proceed with the deposit or redemption logic.
In code, this could look like:
pub fn deposit_with_auth(ctx: Context<DepositWithAuth>, amount: u64) -> Result<()> {
let auth = &ctx.accounts.authorization;
let clock = Clock::get()?;
// 1. Wallet must match signer
require_keys_eq!(auth.wallet, ctx.accounts.user.key(), CustomError::UnauthorizedWallet);
// 2. Authorization must not be expired
require!(clock.unix_timestamp <= auth.expires_at, CustomError::AuthorizationExpired);
// 3. Operation and tranche must match this instruction
require!(auth.operation == OP_DEPOSIT, CustomError::InvalidOperation);
require!(auth.tranche == TRANCHE_SENIOR, CustomError::InvalidTranche);
// 4. If all good, proceed with the normal deposit logic
// (token transfers, share minting, vault accounting, etc.)
process_deposit_into_vault(ctx, amount)?;
Ok(())
}
The important part is that the access control logic lives next to the vault math, not in some external service. If the Authorization PDA is missing, malformed, or expired, the transaction fails at the protocol level, no matter what the frontend or backend tries to do.
Why this pattern matters for institutional RWA
From an institutional perspective, this pattern does three important things at once:
- It makes access rules explicit: auditors can inspect exactly which fields define who can access which tranches and under what conditions.
- It aligns on-chain behavior with off-chain policies: if the identity layer or policy engine refuses to issue an Authorization PDA, the vault is literally unreachable for that combination of wallet, tranche, and operation.
- It avoids long-lived “god whitelists”: authorizations can be short-lived, scoped, and revocable by simply refusing to refresh the PDA once it expires.
For STRATA, Authorization PDAs are the bridge between self-sovereign identity and vault mechanics. They let the protocol enforce complex rules about who can interact with RWA exposure, in a way that is transparent, testable, and as immutable as the rest of the program logic.
If you need help hardening the off-chain side of your crypto project (wallets, backend, domains, or incident response), you can request a security-focused engagement through the Services page or reach out directly via the Contact terminal.