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
| Network | Program ID |
|---|---|
| Devnet | TAMDuaAttest1111111111111111111111111111111 |
| Mainnet | Coming 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 PDAattester_registry(init) - AttesterRegistry PDAverifier_registry(init) - VerifierRegistry PDAauthority(signer, mut) - Program deployersystem_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 PDAconfig(mut) - GlobalConfigattester_registry- AttesterRegistry (validates attester)attester(signer, mut) - Approved attestersystem_program- System program
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) - GlobalConfigattester_registry- AttesterRegistryattester(signer, mut) - Approved attestersystem_program- System program- remaining_accounts - Attestation PDAs to create
BatchAttestationCreated
Verify Attestation
Mark an attestation as verified. Requires approved verifier.
pub fn verify_attestation(ctx: Context<VerifyAttestation>) -> Result<()>
Accounts:
attestation(mut) - Attestation to verifyconfig(mut) - GlobalConfigverifier_registry- VerifierRegistryverifier(signer, mut) - Approved verifier
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) - GlobalConfigverifier_registry- VerifierRegistryverifier(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 attestationconfig- GlobalConfigbounty_pool(mut) - BountyPool PDArecipient(mut) - Rule author walletauthority(signer) - Program authoritysystem_program- System program
- Attestation must be verified
- Bounty not already paid
- Sufficient pool balance
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
| Operation | Compute Units | Fee (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
- Two-step authority transfer prevents accidental lockout
- Registry system controls who can attest/verify
- Pausable allows emergency response
- PDA derivation ensures unique attestations per incident
- Bump validation prevents PDA hijacking
Next Steps
- Self-Hosted Relay - Run your own attestation relay
- Verification - Verify attestations on-chain
- Bounty System - Earn rewards for contributions