This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Solana Attestation Service (SAS) is a Solana program that enables creating, managing, and verifying digital attestations on the Solana blockchain. The system provides a framework for issuers to create schemas and issue attestations that can optionally be tokenized as SPL Token-2022 NFTs.
The repository consists of several main components:
program/: Main Solana program written in Rust using Pinocchio frameworkcore/: Shared types and utilities for schema serializationclients/: Auto-generated client libraries for Rust and TypeScriptintegration_tests/: Comprehensive test suite for all program functionalitycereal_macro/: Procedural macro for schema serialization
# Build the Solana program
cargo-build-sbf
# Run integration tests
cargo-build-sbf && SBF_OUT_DIR=$(pwd)/target/sbf-solana-solana/release cargo test
# Run TypeScript client tests
cd clients/typescript && npm test# Generate IDL using Shank
shank idl -r program -o idl
# Or use the npm script
pnpm run generate-idl# Generate Rust and TypeScript clients from IDL
pnpm run generate-clients# Build TypeScript client
cd clients/typescript && npm run build
# Run tests
cd clients/typescript && npm testThe system consists of three main components:
-
Credential: Represents an issuer's identity and authority to create schemas and attestations
- Contains issuer name and list of authorized signers
- Controls who can create schemas and attestations
-
Schema: Defines the structure and metadata for attestations
- Includes field definitions, layout, and validation rules
- Can be paused/resumed and versioned
- Supports tokenization for creating NFT attestations utilizing Token 2022
-
Attestation: Actual attestation data conforming to a schema
- Contains structured data, expiry time, and nonce
- Can be regular attestations or tokenized (as SPL tokens)
- Can be closed by authorized signers
- Schema Versioning: Schemas can be updated while maintaining backwards compatibility
- Tokenization: Schemas can be tokenized to create NFT-like attestations with metadata
- Access Control: Multi-signature support for credential management
- Event Logging: Comprehensive event system for attestation lifecycle tracking
- Expiry Management: Attestations can have expiration dates
-
program/: Main Solana program written in Rust using Pinocchio framework
- src/processor/: Business logic for each instruction with shared utilities
- shared/: Common utilities (
account_checks.rs,pda_utils.rs,data_utils.rs)
- shared/: Common utilities (
- src/state/: On-chain account structures (
attestation.rs,credential.rs,schema.rs) - src/: Core files with specific purposes:
entrypoint.rs: Program entry point and instruction discriminator routinginstructions.rs: Shank instruction definitions for IDL generationerror.rs: Custom error types and error code definitionsevents.rs: Event definitions for program logging and monitoringconstants.rs: Program constants, seeds, and configuration valueslib.rs: Main library entry point and module declarationsmacros.rs: Helper macros for common operations
- src/processor/: Business logic for each instruction with shared utilities
-
core/: Shared types and utilities for schema serialization
- Primitive data types and schema field validation logic
-
cereal_macro/: Procedural macros for schema serialization
SchemaStructSerializederive macro for automatic byte representation
-
clients/: Auto-generated client libraries for Rust and TypeScript
- rust/src/generated/: Rust client (accounts, instructions, types, errors)
- typescript/src/generated/: TypeScript client (accounts, instructions, programs, types)
- typescript/src/: Client utilities (
pdas.ts,utils.ts) and tests
-
integration_tests/: Comprehensive test suite for all program functionality
- tests/helpers/: Test utility functions and program context setup
- tests/: Individual test files for each instruction type and tokenization
-
scripts/: Build and generation scripts
- Client generation (
generate-clients.js) and IDL processing (process-idl.js)
- Client generation (
-
examples/: Usage examples and practical demonstrations
- typescript/: TypeScript examples for credential setup, schemas, and attestations
-
idl/: Interface Definition Language files
- solana_attestation_service.json: Program IDL defining accounts, instructions, and types
- Program ID:
22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG - Token Program:
TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb(Token-2022) - Event Authority PDA:
DzSpKpST2TSyrxokMXchFz3G2yn5WEGoxzpGEUDjCX4g
The program uses Pinocchio instead of Anchor, chosen for:
- Smaller program size and reduced compute costs
- More control over instruction parsing and execution
- Direct account validation and manipulation
- Shank: Used for IDL generation with
#[derive(ShankInstruction)]and#[account()]attributes - Codama: Transforms Shank-generated IDL into TypeScript and Rust clients
- Automated generation pipeline ensures clients stay in sync with program changes
Uses SPL Token-2022 with extensions for tokenized attestations:
- NonTransferable: Creates soulbound tokens that cannot be transferred
- MetadataPointer: Enables on-chain metadata storage
- GroupMemberPointer: Links attestations to schema token groups
- PermanentDelegate: Gives program permanent control over tokens
- MintCloseAuthority: Allows program to close mints
- Uses instruction discriminator 228 for event emission via CPI
- Prevents log truncation issues common with standard Solana logging
- Emits structured events for attestation lifecycle tracking
All instruction processors must follow this consistent structure:
- Function Signature: Use
#[inline(always)]for all processor functions - Account Destructuring: Use array pattern matching to destructure accounts
- Verification & Security Checks: Use shared verification functions from
processor/shared/ - Business Logic: Implement the core instruction logic
- Event Emission: Emit events if required for the instruction
Example processor structure:
#[inline(always)]
pub fn process_my_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// 1. Account destructuring
let [account1, account2, account3] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
// 2. Verification and security checks
verify_account_owner(account1)?;
verify_pda_derivation(account2, seeds)?;
// 3. Business logic
// ... implementation ...
// 4. Event emission (if required)
emit_event(...)?;
Ok(())
}- Custom error types defined in
program/src/error.rs - Use
AttestationServiceErrorenum for program-specific errors - Convert errors at module boundaries for consistent error propagation
- Custom procedural macro (
cereal_macro) generates schema serialization - Maps Rust types to numeric identifiers for compact representation
- Supports primitive types, vectors, and strings with length prefixes
- Use
#[inline(always)]for ALL instruction processor functions - Minimize heap allocations in program execution
- Keep account sizes as small as possible to reduce storage costs and improve performance
- When account resizing is required, implement it performantly with proper size calculations
- Use stack allocation where possible
- Efficient string and vector handling with length prefixes
- Careful buffer management for variable-length data
- Use PDA utilities from
program/src/processor/shared/pda_utils.rsfor account derivation - Reuse verification functions from
program/src/processor/shared/for consistency - Comprehensive PDA validation for all accounts
- Verify program ownership and account derivation
- Check account mutability and signer requirements
When implementing new instructions:
- Add instruction variant to
AttestationServiceInstructionenum ininstructions.rs - Update discriminator matching in
entrypoint.rs - Implement processor function in
processor/directory with#[inline(always)] - Follow processor code structure:
- Account destructuring using array pattern matching
- Verification and security checks using shared functions from
processor/shared/ - Business logic implementation
- Account resizing if required (implement performantly with proper calculations)
- Event emission if required
- Use PDA utilities from
processor/shared/pda_utils.rsfor account derivation - Add comprehensive tests in
integration_tests/ - Regenerate clients with
pnpm run generate-clients
Schema layouts use numeric type identifiers:
The layout field uses numeric values to specify data types for each field in the schema:
| Value | Data Type | Description |
|---|---|---|
| 0 | U8 | Unsigned 8-bit integer |
| 1 | U16 | Unsigned 16-bit integer |
| 2 | U32 | Unsigned 32-bit integer |
| 3 | U64 | Unsigned 64-bit integer |
| 4 | U128 | Unsigned 128-bit integer |
| 5 | I8 | Signed 8-bit integer |
| 6 | I16 | Signed 16-bit integer |
| 7 | I32 | Signed 32-bit integer |
| 8 | I64 | Signed 64-bit integer |
| 9 | I128 | Signed 128-bit integer |
| 10 | Bool | Boolean value |
| 11 | Char | Single character |
| 12 | String | Variable-length string |
| 13 | VecU8 | Vector of unsigned 8-bit integers |
| 14 | VecU16 | Vector of unsigned 16-bit integers |
| 15 | VecU32 | Vector of unsigned 32-bit integers |
| 16 | VecU64 | Vector of unsigned 64-bit integers |
| 17 | VecU128 | Vector of unsigned 128-bit integers |
| 18 | VecI8 | Vector of signed 8-bit integers |
| 19 | VecI16 | Vector of signed 16-bit integers |
| 20 | VecI32 | Vector of signed 32-bit integers |
| 21 | VecI64 | Vector of signed 64-bit integers |
| 22 | VecI128 | Vector of signed 128-bit integers |
| 23 | VecBool | Vector of boolean values |
| 24 | VecChar | Vector of characters |
| 25 | VecString | Vector of strings |
For example, a layout of [12, 0, 12] would define three fields: a String, followed by a U8 integer, followed by another String. The fieldNames array provides human-readable names that correspond positionally to each layout value, so with field names ["name", "age", "country"], the first String field would be named "name", the U8 field would be "age", and the second String field would be "country". Here's an example of how those would be utilized when creating a Schema:
For tokenized attestations:
- Create regular Credential and Schema
- Tokenize Schema
- Utilize Create Tokenize Attestation instruction (this will create the Attestation and Token)
- Test both success and failure cases
- Use helper functions for common setup operations
- Verify account states before and after operations
- Test edge cases like expired attestations and invalid signers
- TypeScript clients provide typed interfaces for all instructions
- Rust clients use
solana_programtypes for compatibility - Both clients handle account resolution and instruction building
Key seeds and identifiers are defined in program/src/constants.rs:
CREDENTIAL_SEED: For credential PDA derivationSCHEMA_SEED: For schema PDA derivationATTESTATION_SEED: For attestation PDA derivationSCHEMA_MINT_SEED: For tokenized schema mintsATTESTATION_MINT_SEED: For tokenized attestation mintsEVENT_AUTHORITY_SEED: For event authority PDASAS_SEED: For SAS Program Authority PDA;
- Uses Solana CLI tools for program deployment
- Requires
cargo-build-sbffor BPF compilation - Integration tests run against program test framework
- TypeScript development requires Node.js and npm/pnpm
SECURITY IS THE UTMOST PRIORITY - Every instruction must implement comprehensive security checks to prevent exploits and unauthorized access.
All instruction processors MUST implement these security validations:
-
Program Ownership Assertions:
- Assert that all program accounts are owned by the correct program
- Verify system accounts are owned by System Program
- Check token accounts are owned by Token Program
-
Signer Verification:
- Assert required accounts are signers using
account.is_signer() - Verify authorized signers against credential's signer list
- Validate authority accounts have signing privileges
- Assert required accounts are signers using
-
Account Mutability Checks:
- Assert writable accounts using
account.is_writable() - Verify rent-exempt status for account modifications
- Assert writable accounts using
-
Discriminator Validation:
- Assert account discriminators match expected types (Credential, Schema, Attestation)
- Prevent type confusion attacks by validating account data structure
- Check account initialization state
-
PDA Derivation Verification:
- Assert PDA accounts match expected derivation using seeds
- Verify bump seeds to prevent canonical bump attacks
- Validate account addresses against program-derived addresses
- All operations require authorized signer verification against credential's authorized signers list
- Credential owners control schema creation and management
- Only authorized signers can create and close attestations
- Schema pause/resume functionality restricted to credential authority
- Comprehensive schema validation ensures attestation data integrity and type safety
- Account ownership verification prevents unauthorized access
- PDA derivation validation ensures account authenticity
- Instruction data parsing with proper error handling and bounds checking
- Non-transferable tokens prevent unauthorized transfers
- Permanent delegate ensures program maintains control over tokenized attestations
- Mint close authority allows proper cleanup
- Located in
integration_tests/tests/ - Cover all instruction flows and edge cases
- Use helper modules for common operations
- Test both individual instructions and complete workflows
- TypeScript tests in
clients/typescript/test/ - Verify generated client functionality
- Test instruction building and account resolution
- Ensure type safety and proper error handling
- Helper functions in
integration_tests/tests/helpers/ - Common setup for credentials, schemas, and attestations
- Utility functions for account creation and validation
- Test data generators for consistent testing
- Always run
cargo-build-sbfbefore testing program changes - Regenerate clients after modifying instruction interfaces
- Run both Rust and TypeScript tests for comprehensive validation
- Use integration tests to verify end-to-end functionality
- Follow existing patterns for new instruction implementations