A comprehensive predicate registry program for managing attestors, policies, and task validation on Solana. This program provides a decentralized way to register attestors, set client policies, and validate tasks with cryptographic attestations.
- Registry Management: Initialize and manage a decentralized predicate registry
- Attestor Registration: Register and deregister trusted attestors
- Policy Management: Set and update client validation policies
- Task Validation: Validate tasks with cryptographic attestations
- Authority Management: Secure ownership transfer capabilities
- Program Derived Addresses (PDAs): Deterministic account creation for all entities
- Event Emission: Observable state changes for off-chain applications
- Comprehensive Error Handling: Custom error types with descriptive messages
- Authority Management: Secure access control with ownership transfer
- Signature Verification: Ed25519 signature validation for attestations
- Expiration Handling: Time-based validation for tasks and attestations
- Rust 1.70.0 or later
- Solana CLI 1.18.18 or later
- Anchor CLI 0.30.1 or later
- Node.js 18.0 or later
- Yarn (recommended) or npm
-
Clone the repository:
git clone git@github.com:predicatelabs/sol-contracts.git cd sol-contracts -
Install dependencies:
yarn install
-
Build the program:
anchor build
For local testing with solana-test-validator:
# Set cluster to localhost
solana config set --url localhost
# Start local validator (in separate terminal)
solana-test-validatorFor devnet deployment:
-
Set Solana config to devnet:
solana config set --url devnet -
Create/fund your wallet (if needed):
solana-keygen new --outfile ~/.config/solana/id.json solana airdrop 2 -
Deploy to devnet:
./scripts/deploy-devnet.sh
# Run all tests on localnet
anchor test
# Run tests with specific cluster
anchor test --provider.cluster devnet
# Run tests with verbose output
anchor test -- --nocaptureThe program uses Program Derived Addresses (PDAs) for deterministic account creation:
const [registryPda] = PublicKey.findProgramAddressSync(
[Buffer.from("predicate_registry")],
program.programId
);
await program.methods
.initialize()
.accounts({
registry: registryPda,
authority: authority.publicKey,
systemProgram: SystemProgram.programId,
})
.rpc();const [attestorPda] = PublicKey.findProgramAddressSync(
[Buffer.from("attestor"), attestorKey.toBuffer()],
program.programId
);
await program.methods
.registerAttestor(attestorKey)
.accounts({
registry: registryPda,
attestorAccount: attestorPda,
authority: authority.publicKey,
systemProgram: SystemProgram.programId,
})
.rpc();const [policyPda] = PublicKey.findProgramAddressSync(
[Buffer.from("policy"), client.publicKey.toBuffer()],
program.programId
);
// Policy as byte array (max 200 bytes)
const policyData = Buffer.from("your-policy-data", "utf8");
await program.methods
.setPolicy(Array.from(policyData))
.accounts({
registry: registryPda,
policyAccount: policyPda,
client: client.publicKey,
systemProgram: SystemProgram.programId,
})
.rpc();await program.methods
.validateAttestation(task, attestorKey, attestation)
.accounts({
registry: registryPda,
attestorAccount: attestorPda,
policyAccount: policyPda,
validator: validator.publicKey,
})
.rpc();sol-contracts/
βββ programs/predicate_registry/ # Rust program source
β βββ Cargo.toml
β βββ src/
β βββ lib.rs # Main program entry point
β βββ state.rs # Account structures and state management
β βββ errors.rs # Custom error definitions
β βββ events.rs # Event definitions
β βββ instructions/ # Instruction handlers
β βββ mod.rs # Module definitions and contexts
β βββ initialize.rs # Registry initialization
β βββ register_attestor.rs
β βββ deregister_attestor.rs
β βββ set_policy.rs
β βββ update_policy.rs
β βββ validate_attestation.rs
β βββ transfer_authority.rs
βββ migrations/ # Deployment scripts
β βββ deploy.ts
βββ scripts/ # Utility scripts
β βββ deploy-devnet.sh
βββ Anchor.toml # Anchor configuration
βββ Cargo.toml # Workspace configuration
βββ package.json # Node.js dependencies
βββ tsconfig.json # TypeScript configuration
βββ ARCHITECTURE.md # Detailed architecture guide
βββ README.md # This file
pub struct PredicateRegistry {
pub authority: Pubkey, // 32 bytes - Owner of the registry
pub created_at: i64, // 8 bytes - Creation timestamp
pub updated_at: i64, // 8 bytes - Last update timestamp
pub total_attestors: u64, // 8 bytes - Total registered attestors
pub total_policies: u64, // 8 bytes - Total policies set
}pub struct AttestorAccount {
pub attestor: Pubkey, // 32 bytes - Attestor's public key
pub is_registered: bool, // 1 byte - Registration status
pub registered_at: i64, // 8 bytes - Registration timestamp
}pub struct PolicyAccount {
pub client: Pubkey, // 32 bytes - Client's public key
pub policy: [u8; 200], // 200 bytes - Fixed-length policy data
pub policy_len: u16, // 2 bytes - Actual length of policy data
pub set_at: i64, // 8 bytes - Policy creation timestamp
pub updated_at: i64, // 8 bytes - Last update timestamp
}pub struct Task {
pub uuid: [u8; 16], // Unique identifier
pub msg_sender: Pubkey, // Message sender
pub target: Pubkey, // Target address
pub msg_value: u64, // Message value (lamports)
pub encoded_sig_and_args: Vec<u8>, // Encoded signature and arguments
pub policy: [u8; 200], // Fixed-length policy data
pub expiration: i64, // Expiration timestamp
}pub struct Attestation {
pub uuid: [u8; 16], // UUID matching the task
pub attestor: Pubkey, // Attestor's public key
pub signature: [u8; 64], // Ed25519 signature
pub expiration: i64, // Expiration timestamp
}RegistryInitialized: Emitted when registry is createdAttestorRegistered: Emitted when attestor is registeredAttestorDeregistered: Emitted when attestor is deregisteredPolicySet: Emitted when policy is setPolicyUpdated: Emitted when policy is updatedTaskValidated: Emitted when task validation succeedsAuthorityTransferred: Emitted when ownership is transferred
AttestorAlreadyRegistered: Attestor is already registeredAttestorNotRegistered: Attestor is not registeredAttestorNotRegisteredForValidation: Attestor not registered for validationPolicyTooLong: Policy string exceeds 200 charactersInvalidPolicy: Policy string is emptyPolicyNotFound: No existing policy found for clientTaskExpired: Task has expiredAttestationExpired: Attestation has expiredInvalidSignature: Attestation signature is invalidTaskIdMismatch: Task and attestation UUIDs don't matchExpirationMismatch: Task and attestation expirations don't matchWrongAttestor: Signature doesn't match provided attestorUnauthorized: Access control violationsArithmeticError: Arithmetic operation overflow/underflow
- Authority-based Access Control: Registry operations require proper authorization
- Signature Verification: Ed25519 signature validation for attestations
- Expiration Handling: Time-based validation prevents stale data
- PDA-based Addressing: Deterministic and secure account creation
- Input Validation: Parameter validation and sanitization
- Event Auditing: Complete operation history through events
# Development
yarn build # Build the program
yarn test # Run tests
yarn test:devnet # Run tests on devnet
yarn lint # Check code formatting
yarn lint:fix # Fix formatting issues
# Deployment
yarn deploy # Deploy to configured cluster
yarn deploy:devnet # Deploy to devnet# Start local validator (in separate terminal)
solana-test-validator
# Build and deploy
anchor build
anchor deploy
anchor test --skip-deploy --skip-local-validator# Set cluster to devnet
solana config set --url devnet
# Deploy using script
./scripts/deploy-devnet.sh
# Or deploy manually
anchor deploy --provider.cluster devnet
anchor test --provider.cluster devnet# Set cluster to mainnet
solana config set --url mainnet
# Deploy (ensure you have sufficient SOL)
anchor deploy --provider.cluster mainnetThe program ID is declared in lib.rs:
declare_id!("PredicateRegistry11111111111111111111111111");Important: After deployment, update this with your actual program ID and update Anchor.toml accordingly.
- Architecture Guide: Detailed technical documentation
- Anchor Documentation: Framework documentation
- Solana Documentation: Platform documentation
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Ensure all tests pass (
anchor test) - Run linting (
yarn lint:fix) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow Rust and TypeScript best practices
- Add comprehensive tests for new features
- Update documentation for API changes
- Ensure security considerations are addressed
- Use conventional commit messages
This project is licensed under the MIT License - see the LICENSE file for details.
- Anchor Framework for the excellent Solana development framework
- Solana Labs for the high-performance blockchain platform
- The Solana developer community for continuous support and resources