AccountSet

Derive Macro AccountSet 

Source
#[derive(AccountSet)]
{
    // Attributes available to this derive:
    #[account_set]
    #[decode]
    #[validate]
    #[cleanup]
    #[idl]
    #[single_account_set]
}
Expand description

Derives AccountSet lifecycle traits and AccountSetToIdl for a struct.

The AccountSet proc macro generates implementations for the three core traits:

  • AccountSetDecode - Decodes accounts from &[AccountInfo] arrays
  • AccountSetValidate - Validates decoded accounts
  • AccountSetCleanup - Performs cleanup operations after instruction execution

It also generates client-side implementations:

  • CpiAccountSet - Cross-program invocation account handling
  • ClientAccountSet - Client-side account metadata generation
  • `AccountSetToIdl

This macro creates a comprehensive account management system that handles account validation, decoding from account info arrays, cleanup operations, and IDL generation for Solana programs.

§Integration with StarFrameInstruction

When using AccountSet with StarFrameInstruction, the argument types specified in field-level attributes must correspond to the argument types from InstructionArgs. The arg parameter in field attributes should match the types available from the instruction’s decode, validate, run, and cleanup argument types.

§Struct-level Attributes

§#[account_set(skip_client_account_set, skip_cpi_account_set, skip_default_decode, skip_default_validate, skip_default_cleanup, skip_default_idl)]

Controls which implementations are generated:

  • skip_client_account_set - Skips generating ClientAccountSet implementation
  • skip_cpi_account_set - Skips generating CpiAccountSet implementation
  • skip_default_decode - Skips generating default AccountSetDecode implementation
  • skip_default_validate - Skips generating default AccountSetValidate implementation
  • skip_default_cleanup - Skips generating default AccountSetCleanup implementation
  • skip_default_idl - Skips generating default IDL implementations

§#[decode(id = <str>, arg = <type>, generics = <generics>, inline_always)]

Define custom decode implementations with specific arguments:

  • id = <str> - Unique identifier for this decode variant (optional, defaults to no id)
  • arg = <type> - Type of argument passed to decode functions
  • generics = <generics> - Additional generic parameters for this decode implementation
  • inline_always - Whether to add #[inline(always)] to the decode implementation (by default #[inline] is added)

§#[validate(id = <str>, arg = <type>, generics = <generics>, before_validation = <expr>, extra_validation = <expr>, inline_always)]

Define custom validation implementations:

  • id = <str> - Unique identifier for this validate variant (optional, defaults to no id)
  • arg = <type> - Type of argument passed to validate functions
  • generics = <generics> - Additional generic parameters for this validate implementation
  • before_validation = <expr> - Expression to execute before field validation
  • extra_validation = <expr> - Expression to execute after field validation
  • inline_always - Whether to add #[inline(always)] to the validate implementation (by default #[inline] is added)

§#[cleanup(id = <str>, generics = <generics>, arg = <type>, extra_cleanup = <expr>, inline_always)]

Define custom cleanup implementations:

  • id = <str> - Unique identifier for this cleanup variant
  • generics = <generics> - Generic parameters for this cleanup implementation
  • arg = <type> - Type of argument passed to cleanup functions
  • extra_cleanup = <expr> - Cleanup expression to execute after field cleanup
  • inline_always - Whether to add #[inline(always)] to the cleanup implementation (by default #[inline] is added)

§#[idl(id = <str>, arg = <type>, generics = <generics>)]

Define custom IDL generation implementations:

  • id = <str> - Unique identifier for this IDL variant (optional, defaults to no id)
  • arg = <type> - Type of argument passed to IDL functions
  • generics = <generics> - Additional generic parameters for this IDL implementation

§Field-level Attributes

§#[account_set(skip = <TokenStream>)]

Skip this field during account set processing. The field will be initialized with the provided default value.

§#[single_account_set(signer, writable, meta = <expr>, skip_*)]

Mark a field as a single account set. This indicates that the AccountSet contains only one account and all account set traits should be passed through to this flagged field. Only one field can have this attribute.

Options:

  • signer - Mark this account as a signer
  • writable - Mark this account as writable
  • meta = <expr> - Custom metadata expression
  • skip_signed_account - Skip SignedAccount trait implementation
  • skip_writable_account - Skip WritableAccount trait implementation
  • skip_has_inner_type - Skip HasInnerType trait implementation
  • skip_has_owner_program - Skip HasOwnerProgram trait implementation
  • skip_has_seeds - Skip HasSeeds trait implementation
  • skip_can_init_seeds - Skip CanInitSeeds trait implementation
  • skip_can_init_account - Skip CanInitAccount trait implementation

When a field is marked with #[single_account_set], the generated AccountSet implementation will:

  • Implement SingleAccountSet and delegate to the marked field
  • Pass through CpiAccountSet and ClientAccountSet implementations
  • Forward trait implementations like SignedAccount, WritableAccount, HasSeeds, etc.

§#[validate(id = <str>, funder, recipient, skip, requires = [<field>, ...], arg = <expr>, temp = <expr>, arg_ty = <type>, address = <expr>)]

Pass arguments to field validation:

  • id = <str> - Which validate variant this field participates in, to enable multiple AccountSetValidate implementations
  • funder - Mark this field as the funder for the Context cache (only one field can be marked as funder)
  • recipient - Mark this field as the recipient for the Context cache (only one field can be marked as recipient)
  • skip - Skip validation for this field
  • requires = [<field>, ...] - List of fields that must be validated before this field
  • arg = <expr> - Argument to pass to the field’s `AccountSetValidate`` function
  • temp = <expr> - Temporary variable expression to use with arg (requires arg to be specified)
  • arg_ty = <type> - Type of the validation argument. Usually inferred, but can be specified to get better error messages
  • address = <expr> - Check that the field’s key matches this address, expr must return a &Pubkey

§#[decode(id = <str>, arg = <expr>)]

Pass arguments to field decoding:

  • id = <str> - Which decode variant this field participates in, to enable multiple AccountSetDecode implementations
  • arg = <expr> - Argument to pass to the field’s AccountSetDecode function

§#[cleanup(id = <str>, arg = <expr>)]

Pass arguments to field cleanup:

  • id = <str> - Which cleanup variant this field participates in, to enable multiple AccountSetCleanup implementations
  • arg = <expr> - Argument to pass to the field’s AccountSetCleanup function
  • normalize_rent - Mutually exclusive with arg, alias for arg = NormalizeRent(())

§#[idl(id = <str>, arg = <expr>, address = <expr>)]

Pass arguments to IDL generation:

  • id = <str> - Which IDL variant this field participates in, to enable multiple AccountSetToIdl implementations
  • arg = <expr> - Argument to pass to the field’s AccountSetToIdl function for IDL generation
  • address = <expr> - Address expression for single account IDL generation, expr must return a Pubkey

§Examples

§Basic Account Set

use star_frame::prelude::*;

#[derive(AccountSet)]
pub struct BasicAccounts {
    pub authority: Signer,
    pub account: Mut<SystemAccount>,
    pub system_program: Program<System>,
}

§Account Set with Custom Arguments

use star_frame::prelude::*;

#[derive(AccountSet)]
#[decode(arg = usize)]
#[validate(arg = String, extra_validation = self.check_authority(arg))]
pub struct CustomAccounts {
    pub authority: Signer,
    // One of Vec's AccountSetDecode implementations takes in a usize to specify the number of accounts to decode, so it will try to decode `arg * 2` (for some reason?) accounts
    // which will be passed to the `AccountSetDecode` function from StarFrameInstruction as the decode arg
    #[decode(arg = arg * 2)]
    pub accounts: Vec<SystemAccount>,
}

impl CustomAccounts {
    fn check_authority(&self, arg: String) -> Result<()> {
        todo!("check stuff")
    }
}

By setting the decode arg to usize, and validate to String, any StarFrameInstruction using this set must have an InstructionArgs implementation that returns those types.

§Single Account Set Newtype

use star_frame::{prelude::*, derive_more};

#[derive(AccountSet, derive_more::Deref, derive_more::DerefMut, Debug)]
pub struct WrappedCounter(#[single_account_set] Account<CounterAccount>);

This creates a newtype wrapper that implements AccountSet and passes through all account traits to the inner Account<CounterAccount>. The signer and writable flags modify the account’s metadata for CPI and client usage. This will propagate all of the account_set::modifier marker traits from the inner account to the newtype.