Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/blueshift-gg/quasar/llms.txt

Use this file to discover all available pages before exploring further.

Quasar provides two account wrappers for situations where the standard validation pipeline is intentionally bypassed. UncheckedAccount skips all account checks entirely, giving direct access to the raw AccountView. Uninit<A> is a parse-time placeholder for an account slot that needs to be created and initialized by the handler via a system program CPI.

UncheckedAccount

UncheckedAccount is defined with an empty check list, meaning no owner, no signer, no executable, and no data validation is performed:
define_account!(
    /// An account with no validation.
    ///
    /// Useful for accounts passed through to CPI calls or whose
    /// constraints are checked manually by the instruction handler.
    pub struct UncheckedAccount => []
);

impl AccountLoad for UncheckedAccount {
    fn check(_view: &AccountView) -> Result<(), ProgramError> {
        Ok(())
    }
}
The wrapper is #[repr(transparent)] over AccountView (via the define_account! macro), so there is no runtime overhead compared to working directly with the raw account.
UncheckedAccount performs no security checks. Any property you care about — owner, address, data format, signer status, writability — must be validated manually in your handler. Failing to validate can lead to critical vulnerabilities such as arbitrary account substitution or unauthorized data modification.

When to Use UncheckedAccount

CPI passthrough

When forwarding an account to another program without inspecting its data yourself. The downstream program is responsible for validation.

Manual validation

When your validation logic is more complex than Quasar’s constraint DSL supports — for example, checking an external program’s account layout by reading raw bytes.

Destination accounts

Accounts that only receive lamports (like a close destination). No data check is needed.

External program accounts

Accounts from third-party programs whose layout Quasar doesn’t know about. Check manually after receiving them.

Accessing UncheckedAccount Data

UncheckedAccount inherits all AccountView accessors through its transparent layout. Use to_account_view() to obtain the raw view:
pub fn handler(ctx: Ctx<MyAccounts>) -> Result<(), ProgramError> {
    let raw: &AccountView = ctx.accounts.my_account.to_account_view();

    // Address
    let addr: &Address = ctx.accounts.my_account.address();

    // Lamports
    let lamports: u64 = raw.lamports();

    // Data length
    let data_len: usize = raw.data_len();

    // Safe borrow of account data
    let data = raw.try_borrow()?;

    // ...
    Ok(())
}

Escrow Example: UncheckedAccount as lamport recipient

The Take instruction uses UncheckedAccount for the maker field because the maker receives lamports from the closed escrow but does not need to sign the take transaction:
// From examples/escrow/src/instructions/take.rs
#[derive(Accounts)]
pub struct Take {
    #[account(mut)]
    pub taker: Signer,
    #[account(
        mut,
        has_one(maker),          // validates escrow.maker == maker.address()
        has_one(maker_ta_b),
        constraints(escrow.receive > 0),
        close(dest = taker),
    )]
    pub escrow: Account<Escrow>,

    // maker receives lamports but doesn't sign — UncheckedAccount is appropriate
    #[account(mut)]
    pub maker: UncheckedAccount,
    // ...
}
The has_one(maker) constraint on escrow ensures that maker.address() matches the stored maker address in the escrow account, so although maker is unchecked in terms of type, its identity is still validated via the constraint.

Writing Bytes to UncheckedAccount

AccountDataWrite provides a safe byte-level write operation:
pub trait AccountDataWrite {
    fn write_bytes(&mut self, offset: usize, bytes: &[u8]) -> Result<(), ProgramError>;
}
Both AccountView and UncheckedAccount implement this trait. It validates writability and bounds before copying:
// Write 4 bytes at offset 8
ctx.accounts.my_account.write_bytes(8, &[1, 2, 3, 4])?;
write_bytes returns ProgramError::Immutable if the account is not writable, and ProgramError::AccountDataTooSmall if the write would overflow the data region.

Uninit<A>

Uninit<A> is a parse-time marker for an account slot that must be initialized by the handler. Unlike UncheckedAccount, it does perform one validation: the account must be owned by the System program (all-zeros address). If the account is already initialized (owner is not System), parsing fails with ProgramError::AccountAlreadyInitialized.
impl<A> AccountLoad for Uninit<A> {
    fn check(view: &AccountView) -> Result<(), ProgramError> {
        if !is_system_program(view.owner()) {
            return Err(ProgramError::AccountAlreadyInitialized);
        }
        Ok(())
    }
}

The DeferredInit Trait

Initialization happens in the handler by calling .init(payer, params) or .init_signed(payer, params, signers). The params value must implement DeferredInit<A>:
pub trait DeferredInit<A> {
    fn init_uninit<'a>(
        self,
        target: &'a mut AccountView,
        payer: &AccountView,
        signers: &[Signer<'_, '_>],
    ) -> Result<&'a mut A, ProgramError>;
}
For Quasar program accounts (Account<T>), the T::Target (the inner data struct) already implements DeferredInit<Account<T>> automatically. The implementation:
  1. Verifies the account is still owned by System
  2. Fetches rent from the sysvar
  3. Calls create_account CPI to allocate T::SPACE bytes and transfer lamports
  4. Writes T::DISCRIMINATOR at the start of data
  5. Copies the provided data into the account
  6. Re-validates the account as Account<T>
  7. Returns &mut Account<T>

Uninit Usage

#[derive(Accounts)]
pub struct CreateVault {
    #[account(mut)]
    pub payer: Signer,
    pub new_vault: Uninit<Account<Vault>>,
    pub system_program: Program<SystemProgram>,
}

pub fn handler(mut ctx: Ctx<CreateVault>) -> Result<(), ProgramError> {
    let vault: &mut Account<Vault> = ctx.accounts.new_vault.init(
        &ctx.accounts.payer,
        VaultData {
            owner: *ctx.accounts.payer.address(),
            balance: PodU64::from(0),
        },
    )?;

    // vault is now a fully initialized, validated Account<Vault>
    Ok(())
}

init_signed for PDA Accounts

When the new account is a PDA that must sign its own creation, use init_signed:
let vault = ctx.accounts.new_vault.init_signed(
    &ctx.accounts.payer,
    VaultData { /* ... */ },
    &[Signer::from(&[b"vault", &[bump]])],
)?;
In practice, most programs use the #[account(init, payer = ...)] constraint instead of Uninit<A> directly. The constraint generates the same initialization code and is more concise. Uninit<A> is available for cases where you need fine-grained control over when and how initialization happens within the handler.

Build docs developers (and LLMs) love