9 min read Updated July 6, 2026

Solana Program

Tamandua's on-chain attestation interface is documented for devnet/prototype integrations using the Anchor framework. Mainnet deployment, public bounty settlement, and treasury operations remain gated.

Program Address

NetworkProgram ID
DevnetTAMDuaAttest1111111111111111111111111111111
MainnetComing soon

Architecture Overview

[Tamandua Backend] --> [Relay] --> [create_attestation/batch_create_attestations]
                                           |
                                           v
                                    [Attestation PDA]
                                           |
                       [Verifier] --> [verify_attestation]
                                           |
                                           v
                       [Authority] --> [claim_bounty] --> [Rule Author]

Account Structure

GlobalConfig

Singleton PDA storing program-wide settings.

#[account]
pub struct GlobalConfig {
    /// Program authority (admin)
    pub authority: Pubkey,

    /// Pending authority for two-step transfer
    pub pending_authority: Option<Pubkey>,

    /// Whether the program is paused
    pub paused: bool,

    /// Default bounty amount in lamports
    pub default_bounty_lamports: u64,

    /// Minimum bounty amount in lamports
    pub min_bounty_lamports: u64,

    /// Maximum bounty amount in lamports
    pub max_bounty_lamports: u64,

    /// Total number of attestations created
    pub total_attestations: u64,

    /// Total number of verified attestations
    pub total_verified: u64,

    /// Total bounties paid in lamports
    pub total_bounties_paid: u64,

    /// PDA bump seed
    pub bump: u8,
}
Seeds: ["config"]

Attestation

PDA storing a single incident attestation.

#[account]
pub struct Attestation {
    /// SHA256 hash of the redacted incident payload
    pub incident_hash: [u8; 32],

    /// SHA256 hash of the manifest/signature used for detection
    pub manifest_hash: [u8; 32],

    /// Severity level (1-5)
    pub severity: u8,

    /// MITRE ATT&CK technique ID (e.g., "T1555.003")
    pub mitre_technique: String,  // max 16 chars

    /// Unix timestamp when the incident occurred
    pub timestamp: i64,

    /// SHA256 hash of the organization ID (pseudonymized)
    pub organization_pseudonym: [u8; 32],

    /// Whether this attestation has been verified
    pub verified: bool,

    /// SHA256 hash of the detection rule that triggered
    pub rule_hash: [u8; 32],

    /// SHA256 hash of the agent ID (pseudonymized)
    pub agent_pseudonym: [u8; 32],

    /// Unix timestamp when this attestation was created on-chain
    pub attestation_timestamp: i64,

    /// Public key of the attester who created this
    pub attester: Pubkey,

    /// Unix timestamp when verified (if applicable)
    pub verified_at: Option<i64>,

    /// Public key of the verifier (if applicable)
    pub verifier: Option<Pubkey>,

    /// Whether the bounty has been paid
    pub bounty_paid: bool,

    /// Amount paid in lamports (if bounty was paid)
    pub bounty_amount: Option<u64>,

    /// Recipient of the bounty (if paid)
    pub bounty_recipient: Option<Pubkey>,

    /// PDA bump seed
    pub bump: u8,
}
Seeds: ["attestation", incident_hash]

BountyPool

PDA holding SOL for bounty payouts.

#[account]
pub struct BountyPool {
    /// Authority that can withdraw from the pool
    pub authority: Pubkey,

    /// Total amount deposited into the pool
    pub total_deposited: u64,

    /// Total amount paid out from the pool
    pub total_paid_out: u64,

    /// Whether deposits are currently accepted
    pub accepting_deposits: bool,

    /// PDA bump seed
    pub bump: u8,
}
Seeds: ["bounty_pool"]

AttesterRegistry

Registry of approved attesters (relay servers, backend instances).

#[account]
pub struct AttesterRegistry {
    /// Authority that can modify this registry
    pub authority: Pubkey,

    /// Number of approved attesters
    pub count: u32,

    /// List of approved attester public keys (max 50)
    pub attesters: Vec<Pubkey>,

    /// PDA bump seed
    pub bump: u8,
}
Seeds: ["attester_registry"]

VerifierRegistry

Registry of approved verifiers (security analysts, automated systems).

#[account]
pub struct VerifierRegistry {
    /// Authority that can modify this registry
    pub authority: Pubkey,

    /// Number of approved verifiers
    pub count: u32,

    /// List of approved verifier public keys (max 50)
    pub verifiers: Vec<Pubkey>,

    /// PDA bump seed
    pub bump: u8,
}
Seeds: ["verifier_registry"]

Instructions

Initialize

Initialize program configuration and registries. Called once after deployment.

pub fn initialize(
    ctx: Context<Initialize>,
    default_bounty_lamports: u64,
    min_bounty_lamports: u64,
    max_bounty_lamports: u64,
) -> Result<()>
Accounts:
  • config (init) - GlobalConfig PDA
  • attester_registry (init) - AttesterRegistry PDA
  • verifier_registry (init) - VerifierRegistry PDA
  • authority (signer, mut) - Program deployer
  • system_program - System program

Create Attestation

Create a new incident attestation. Requires approved attester.

pub fn create_attestation(
    ctx: Context<CreateAttestation>,
    incident_hash: [u8; 32],
    manifest_hash: [u8; 32],
    severity: u8,
    mitre_technique: String,
    timestamp: i64,
    organization_pseudonym: [u8; 32],
    rule_hash: [u8; 32],
    agent_pseudonym: [u8; 32],
) -> Result<()>
Accounts:
  • attestation (init) - Attestation PDA
  • config (mut) - GlobalConfig
  • attester_registry - AttesterRegistry (validates attester)
  • attester (signer, mut) - Approved attester
  • system_program - System program

Emits: IncidentAttested

Batch Create Attestations

Create multiple attestations in a single transaction. More efficient for relay servers.

pub fn batch_create_attestations<'info>(
    ctx: Context<'_, '_, '_, 'info, BatchCreateAttestations<'info>>,
    attestations: Vec<BatchAttestationInput>,
) -> Result<()>
Max batch size: 10 attestations Accounts:
  • config (mut) - GlobalConfig
  • attester_registry - AttesterRegistry
  • attester (signer, mut) - Approved attester
  • system_program - System program
  • remaining_accounts - Attestation PDAs to create

Emits: BatchAttestationCreated

Verify Attestation

Mark an attestation as verified. Requires approved verifier.

pub fn verify_attestation(ctx: Context<VerifyAttestation>) -> Result<()>
Accounts:
  • attestation (mut) - Attestation to verify
  • config (mut) - GlobalConfig
  • verifier_registry - VerifierRegistry
  • verifier (signer, mut) - Approved verifier

Emits: AttestationVerified

Batch Verify Attestations

Verify multiple attestations in a single transaction.

pub fn batch_verify_attestations<'info>(
    ctx: Context<'_, '_, '_, 'info, BatchVerifyAttestations<'info>>,
) -> Result<()>
Accounts:
  • config (mut) - GlobalConfig
  • verifier_registry - VerifierRegistry
  • verifier (signer, mut) - Approved verifier
  • remaining_accounts - Attestation PDAs to verify

Initialize Bounty Pool

Create the bounty pool PDA. Authority only.

pub fn initialize_bounty_pool(ctx: Context<InitializeBountyPool>) -> Result<()>

Fund Bounty Pool

Deposit SOL into the bounty pool. Anyone can fund.

pub fn fund_bounty_pool(ctx: Context<FundBountyPool>, amount_lamports: u64) -> Result<()>
Emits: BountyPoolFunded

Claim Bounty

Planned bounty-claim instruction shape for a reviewed attestation. This is not a live public payout path until treasury, antifraud, reviewer, and mainnet gates pass.

pub fn claim_bounty(ctx: Context<ClaimBounty>, amount_lamports: Option<u64>) -> Result<()>
Accounts:
  • attestation (mut) - Verified attestation
  • config - GlobalConfig
  • bounty_pool (mut) - BountyPool PDA
  • recipient (mut) - Rule author wallet
  • authority (signer) - Program authority
  • system_program - System program

Requirements:
  • Attestation must be verified
  • Bounty not already paid
  • Sufficient pool balance

Emits: BountyPaid

Registry Management

// Add/remove attesters
pub fn add_attester(ctx: Context<AddAttester>, attester: Pubkey) -> Result<()>
pub fn remove_attester(ctx: Context<RemoveAttester>, attester: Pubkey) -> Result<()>
pub fn batch_add_attesters(ctx: Context<BatchAddAttesters>, attesters: Vec<Pubkey>) -> Result<()>

// Add/remove verifiers
pub fn add_verifier(ctx: Context<AddVerifier>, verifier: Pubkey) -> Result<()>
pub fn remove_verifier(ctx: Context<RemoveVerifier>, verifier: Pubkey) -> Result<()>
pub fn batch_add_verifiers(ctx: Context<BatchAddVerifiers>, verifiers: Vec<Pubkey>) -> Result<()>
Emits: AttesterUpdated, VerifierUpdated

Authority Management

Two-step authority transfer for safety.

pub fn initiate_authority_transfer(
    ctx: Context<InitiateAuthorityTransfer>,
    new_authority: Pubkey,
) -> Result<()>

pub fn accept_authority_transfer(ctx: Context<AcceptAuthorityTransfer>) -> Result<()>

pub fn cancel_authority_transfer(ctx: Context<CancelAuthorityTransfer>) -> Result<()>
Emits: AuthorityTransferred

Program Control

pub fn set_program_paused(ctx: Context<SetProgramPaused>, paused: bool) -> Result<()>

pub fn update_bounty_params(
    ctx: Context<UpdateBountyParams>,
    default_bounty_lamports: Option<u64>,
    min_bounty_lamports: Option<u64>,
    max_bounty_lamports: Option<u64>,
) -> Result<()>

Error Codes

#[error_code]
pub enum AttestationError {
    #[msg("Program is paused")]
    ProgramPaused,

    #[msg("Unauthorized attester")]
    UnauthorizedAttester,

    #[msg("Unauthorized verifier")]
    UnauthorizedVerifier,

    #[msg("Attestation already verified")]
    AlreadyVerified,

    #[msg("Attestation not verified")]
    NotVerified,

    #[msg("Bounty already paid")]
    BountyAlreadyPaid,

    #[msg("Insufficient bounty pool balance")]
    InsufficientPoolBalance,

    #[msg("Bounty amount below minimum")]
    BountyBelowMinimum,

    #[msg("Bounty amount above maximum")]
    BountyAboveMaximum,

    #[msg("Batch size exceeds maximum")]
    BatchSizeExceeded,

    #[msg("Registry is full")]
    RegistryFull,

    #[msg("Attester already registered")]
    AttesterAlreadyRegistered,

    #[msg("Verifier already registered")]
    VerifierAlreadyRegistered,

    #[msg("MITRE technique too long")]
    MitreTechniqueTooLong,
}

Events

#[event]
pub struct IncidentAttested {
    pub incident_hash: [u8; 32],
    pub manifest_hash: [u8; 32],
    pub severity: u8,
    pub mitre_technique: String,
    pub timestamp: i64,
    pub attestation_timestamp: i64,
    pub attester: Pubkey,
}

#[event]
pub struct AttestationVerified {
    pub incident_hash: [u8; 32],
    pub verifier: Pubkey,
    pub verified_at: i64,
}

#[event]
pub struct BountyPaid {
    pub incident_hash: [u8; 32],
    pub recipient: Pubkey,
    pub amount_lamports: u64,
}

#[event]
pub struct BatchAttestationCreated {
    pub batch_size: u8,
    pub attester: Pubkey,
    pub timestamp: i64,
}

#[event]
pub struct BountyPoolFunded {
    pub depositor: Pubkey,
    pub amount_lamports: u64,
    pub new_balance: u64,
}

#[event]
pub struct AuthorityTransferred {
    pub old_authority: Pubkey,
    pub new_authority: Pubkey,
}

Integration Examples

TypeScript/JavaScript

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { TamanduaAttestations } from "../target/types/tamandua_attestations";

const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);

const program = anchor.workspace.TamanduaAttestations as Program<TamanduaAttestations>;

// Derive PDAs
const [configPda] = anchor.web3.PublicKey.findProgramAddressSync(
  [Buffer.from("config")],
  program.programId
);

const incidentHash = crypto.createHash('sha256').update('incident_data').digest();
const [attestationPda] = anchor.web3.PublicKey.findProgramAddressSync(
  [Buffer.from("attestation"), incidentHash],
  program.programId
);

// Create attestation
await program.methods
  .createAttestation(
    Array.from(incidentHash),           // incident_hash
    Array.from(manifestHash),           // manifest_hash
    4,                                   // severity (high)
    "T1555.003",                         // mitre_technique
    new anchor.BN(Date.now() / 1000),   // timestamp
    Array.from(orgPseudonym),           // organization_pseudonym
    Array.from(ruleHash),               // rule_hash
    Array.from(agentPseudonym)          // agent_pseudonym
  )
  .accounts({
    attestation: attestationPda,
    config: configPda,
    attesterRegistry: attesterRegistryPda,
    attester: attesterKeypair.publicKey,
    systemProgram: anchor.web3.SystemProgram.programId,
  })
  .signers([attesterKeypair])
  .rpc();

// Verify attestation
await program.methods
  .verifyAttestation()
  .accounts({
    attestation: attestationPda,
    config: configPda,
    verifierRegistry: verifierRegistryPda,
    verifier: verifierKeypair.publicKey,
  })
  .signers([verifierKeypair])
  .rpc();

// Fetch attestation data
const attestation = await program.account.attestation.fetch(attestationPda);
console.log("Verified:", attestation.verified);
console.log("Severity:", attestation.severity);
console.log("MITRE:", attestation.mitreTechnique);

Rust (Backend Integration)

use anchor_client::solana_sdk::{
    commitment_config::CommitmentConfig,
    signature::{Keypair, Signer},
};
use anchor_client::Client;

fn submit_attestation(
    incident_hash: [u8; 32],
    severity: u8,
    mitre_technique: &str,
) -> Result<Signature> {
    let cluster = Cluster::Devnet;
    let payer = Keypair::read_from_file("~/.config/solana/id.json")?;
    let client = Client::new(cluster, &payer);
    let program = client.program(PROGRAM_ID)?;

    // Derive PDAs
    let (config_pda, _) = Pubkey::find_program_address(
        &[b"config"],
        &PROGRAM_ID,
    );

    let (attestation_pda, _) = Pubkey::find_program_address(
        &[b"attestation", &incident_hash],
        &PROGRAM_ID,
    );

    // Send transaction
    let signature = program
        .request()
        .accounts(CreateAttestation {
            attestation: attestation_pda,
            config: config_pda,
            attester_registry: attester_registry_pda,
            attester: payer.pubkey(),
            system_program: system_program::ID,
        })
        .args(tamandua_attestations::instruction::CreateAttestation {
            incident_hash,
            manifest_hash: compute_manifest_hash(),
            severity,
            mitre_technique: mitre_technique.to_string(),
            timestamp: chrono::Utc::now().timestamp(),
            organization_pseudonym: compute_org_pseudonym(),
            rule_hash: compute_rule_hash(),
            agent_pseudonym: compute_agent_pseudonym(),
        })
        .signer(&payer)
        .send()?;

    Ok(signature)
}

Devnet Testing

Setup

# Install Solana CLI
sh -c "$(curl -sSfL https://release.solana.com/v1.18.0/install)"

# Configure for devnet
solana config set --url devnet

# Create keypair
solana-keygen new -o ~/.config/solana/id.json

# Airdrop SOL
solana airdrop 2

Deploy Program (Development)

cd apps/tamandua_solana

# Build
anchor build

# Deploy to devnet
anchor deploy --provider.cluster devnet

# Initialize
anchor run initialize

Test Integration

# Run Anchor tests
anchor test --provider.cluster devnet

# Or with local validator
solana-test-validator &
anchor test

Cost Estimates

OperationCompute UnitsFee (lamports)Fee (USD @ $100/SOL)
Create attestation~50,000~5,000$0.0005
Batch create (10)~200,000~20,000$0.002
Verify attestation~30,000~3,000$0.0003
Claim bounty~40,000~4,000$0.0004
Fund pool~20,000~2,000$0.0002

Security Considerations

  1. Two-step authority transfer prevents accidental lockout
  2. Registry system controls who can attest/verify
  3. Pausable allows emergency response
  4. PDA derivation ensures unique attestations per incident
  5. Bump validation prevents PDA hijacking

Next Steps