Prepared by: Codeburt (FixCore Development Intelligence) Date: 2026-03-19 Source repo: github.com/QuantuLabs/8004-solana Purpose: Document exact CPI signatures, devnet program IDs, quirks, and limitations for Preventra Protocol integration. Status: Research complete. Awaiting Stratburt review before writing Anchor code.
| Program | Devnet | Localnet |
|---|---|---|
| Agent Registry 8004 | 8oo4J9tBB3Hna1jRQ3rWvJjojqM5DYTDJo5cejUuJy3C |
8oo4dC4JvBLwy5tGgiH3WwK4B9PWxL9Z4XjA2jzkQMbQ |
| ATOM Engine | AToMufS4QD6hEXvcvBDg9m1AHeCLpmZQsyfYa5h9MwAF |
same |
| Metaplex Core | CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d |
cloned from devnet |
Devnet liveness verified: 2026-03-19 via solana account. Program is executable, owned by BPFLoaderUpgradeable. Balance: 0.00114144 SOL.
IDL version: agent_registry_8004 v0.5.2, atom_engine v0.2.2
pub fn register(ctx: Context<Register>, agent_uri: String) -> Result<()>Alternative with ATOM opt-out:
pub fn register_with_options(
ctx: Context<Register>,
agent_uri: String,
atom_enabled: bool,
) -> Result<()>| Account | Type | Writable | Signer | PDA Seeds |
|---|---|---|---|---|
root_config |
RootConfig | no | no | ["root_config"] |
registry_config |
RegistryConfig | no | no | ["registry_config", collection.key()] |
agent_account |
AgentAccount (init) | yes | no | ["agent", asset.key()] |
asset |
UncheckedAccount | yes | yes | N/A (fresh Keypair) |
collection |
UncheckedAccount | yes | no | Known from RootConfig |
owner |
Signer | yes | yes | Wallet paying for tx |
system_program |
Program | no | no | 11111111111111111111111111111111 |
mpl_core_program |
Program | no | no | CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d |
- Validates
agent_urilength (max 250 bytes) - CPIs into Metaplex Core
CreateV2to mint a new Core asset (NFT) - Initializes
AgentAccountPDA with: collection, creator (= owner at registration), owner, asset, bump, atom_enabled (default true), empty digests/counts, empty agent_uri set to provided value, nft_name set to "Agent"
import { Keypair, SystemProgram, PublicKey } from "@solana/web3.js";
const assetKeypair = Keypair.generate();
const [agentPda] = PublicKey.findProgramAddressSync(
[Buffer.from("agent"), assetKeypair.publicKey.toBuffer()],
REGISTRY_PROGRAM_ID
);
const [rootConfigPda] = PublicKey.findProgramAddressSync(
[Buffer.from("root_config")],
REGISTRY_PROGRAM_ID
);
// collection pubkey must be fetched from rootConfig.baseCollection
const rootConfig = await program.account.rootConfig.fetch(rootConfigPda);
const collectionPubkey = rootConfig.baseCollection;
const [registryConfigPda] = PublicKey.findProgramAddressSync(
[Buffer.from("registry_config"), collectionPubkey.toBuffer()],
REGISTRY_PROGRAM_ID
);
await program.methods
.register("https://example.com/agent-metadata.json")
.accounts({
rootConfig: rootConfigPda,
registryConfig: registryConfigPda,
agentAccount: agentPda,
asset: assetKeypair.publicKey,
collection: collectionPubkey,
owner: wallet.publicKey,
payer: wallet.publicKey,
systemProgram: SystemProgram.programId,
mplCoreProgram: new PublicKey("CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d"),
})
.signers([assetKeypair])
.rpc();This is the most important finding for Preventra integration.
The Register context requires:
pub asset: Signer<'info> // Fresh keypair that signs the transactionThis means the asset account (which becomes the Metaplex Core NFT) must be a new Keypair that signs the transaction. In a CPI from Preventra's register() instruction, we face a fundamental problem:
- Preventra's program cannot generate a new Keypair during execution
- Preventra's program cannot sign on behalf of an externally-generated keypair (it is not a PDA of Preventra)
- The asset keypair must be passed as a signer from the client, which means the client must generate it
Option A: Two-Transaction Approach (Recommended for Phase 0)
- Client generates asset Keypair
- Transaction 1: Client calls QuantuLabs
register()directly via SDK, passing asset Keypair as signer - Transaction 2: Client calls Preventra
register()with the asset pubkey asagent_identity - Preventra reads the QuantuLabs AgentAccount PDA to verify the identity exists and the caller owns it
Pros: Simple, no CPI complexity, works today. Cons: Two transactions, identity creation is not atomic with vault/provenance creation.
Option B: Passthrough CPI (Complex, Phase 1+)
- Client generates asset Keypair and passes it to Preventra's
register() - Preventra CPIs into QuantuLabs
register(), forwarding the asset Keypair as a signer - Anchor supports forwarding external signers via
ctx.remaining_accountsor explicit account passthrough
Pros: Single transaction, atomic. Cons: Complex account plumbing, 8+ accounts to forward, compute budget concerns, tight coupling to QuantuLabs account layout.
Option C: Mock Identity (De-risk, approved by Stratburt)
- Use a Keypair-generated Pubkey as
agent_identityfor Steps 3-4 - No QuantuLabs dependency during core IP development
- Layer in real integration (Option A or B) in Step 5
Pros: Core IP never blocked by external dependency. Cons: None for Phase 0.
Follow Stratburt's approved de-risk strategy:
- Steps 3-4: Mock identity (Option C)
- Step 5: Option A (two-transaction) for initial integration
- Phase 1: Evaluate Option B if single-transaction atomicity becomes a requirement
pub struct AgentAccount {
pub collection: Pubkey, // Collection this agent belongs to
pub creator: Pubkey, // Immutable, set at registration
pub owner: Pubkey, // Cached from Core asset
pub asset: Pubkey, // Metaplex Core asset (unique ID)
pub bump: u8, // PDA bump
pub atom_enabled: bool, // One-way flag
pub agent_wallet: Option<Pubkey>, // Ed25519 verified wallet
pub feedback_digest: [u8; 32], // Hash-chain for feedback
pub feedback_count: u64,
pub response_digest: [u8; 32], // Hash-chain for responses
pub response_count: u64,
pub revoke_digest: [u8; 32], // Hash-chain for revocations
pub revoke_count: u64,
pub parent_asset: Option<Pubkey>, // Parent link (first-write-wins)
pub parent_locked: bool,
pub col_locked: bool, // Collection pointer lock
pub agent_uri: String, // Max 250 bytes
pub nft_name: String, // Max 32 bytes
pub col: String, // Max 128 bytes, format: c1:<cid_norm>
}PDA derivation: [b"agent", asset.key().as_ref()] using the QuantuLabs program ID.
| PDA | Seeds | Program |
|---|---|---|
| RootConfig | ["root_config"] |
Agent Registry |
| RegistryConfig | ["registry_config", collection.key()] |
Agent Registry |
| AgentAccount | ["agent", asset.key()] |
Agent Registry |
| MetadataEntryPda | ["agent_meta", asset.key(), key_hash[0..16]] |
Agent Registry |
| AtomConfig | ["atom_config"] |
ATOM Engine |
| AtomStats | ["atom_stats", asset.key()] |
ATOM Engine |
| Registry CPI Authority | ["atom_cpi_authority"] |
Agent Registry |
These are QuantuLabs instructions Preventra's SDK can wrap for agent management:
| Instruction | Signer | Purpose |
|---|---|---|
set_agent_uri(new_uri) |
Owner | Update agent metadata URI |
set_metadata_pda(key_hash, key, value, immutable) |
Owner | Set individual metadata key-value |
delete_metadata_pda(key_hash) |
Owner | Delete metadata, recover rent |
enable_atom() |
Owner | One-way ATOM Engine activation |
sync_owner() |
Permissionless | Refresh cached owner from Core asset |
transfer_agent() |
Owner | Transfer with automatic owner sync |
set_agent_wallet(new_wallet, deadline) |
Owner | Ed25519 signature verified wallet set |
set_collection_pointer(col) |
Creator | First-write-wins, c1:cid format |
set_parent_asset(parent_asset) |
Owner | First-write-wins parent link |
owner_of() |
N/A (view) | Get cached owner |
core_owner_of() |
N/A (view) | Get live owner from Metaplex Core |
Note: The IDL reports error codes starting at 12000 (Anchor base 6000 + 6000 offset). The Rust source uses Anchor's default numbering starting at 6000. The actual on-chain codes match the IDL values (12000+).
Key error codes relevant to Preventra integration:
| Code (IDL) | Name | Message |
|---|---|---|
| 12000 | UriTooLong | URI exceeds 250 bytes |
| 12004 | Unauthorized | Unauthorized |
| 12010 | InvalidCollection | Invalid collection |
| 12011 | InvalidAsset | Invalid asset |
| 12251 | RootAlreadyInitialized | Root config already initialized |
| 12400 | InvalidProgram | Invalid program ID for CPI call |
| Package | QuantuLabs Version | Notes |
|---|---|---|
@coral-xyz/anchor |
0.31.1 | Preventra uses Anchor 0.32.1, should be compatible |
@metaplex-foundation/js |
^0.20.1 | For Metaplex Core operations |
@solana/web3.js |
Standard | No version conflicts expected |
tweetnacl |
^1.0.3 | For Ed25519 signature operations |
Anchor version gap: QuantuLabs built with 0.31.1, Preventra will use 0.32.1. The IDL format is compatible (both use spec 0.1.0). CPI calls do not depend on matching Anchor versions -- they use raw instruction data. No issues expected.
Before Preventra can register agents via QuantuLabs on devnet, the QuantuLabs registry must be initialized. This requires:
- The registry
Initializeinstruction must have been called by the upgrade authority of the QuantuLabs program - This creates
RootConfigandRegistryConfigPDAs, and a Metaplex Core Collection - The
collectionaddress is stored inRootConfig.base_collection
Risk: If the QuantuLabs devnet registry is not initialized, or gets reset, Preventra cannot register identities. This is another reason the mock identity de-risk strategy is correct for Phase 0.
Mitigation: In Step 5, the Preventra SDK should first verify the RootConfig PDA exists and is valid before attempting registration. If it does not exist, return a clear error: "QuantuLabs registry not initialized on this cluster."
-
NFT name is hardcoded to "Agent" in the registration instruction. The
nft_namefield onAgentAccountstores this, but there is no way to set a custom name at registration time. Agent differentiation comes from theagent_urimetadata link. -
No sequential agent IDs. QuantuLabs uses the
assetPubkey as the unique identifier (EVM conformity design). This means Preventra cannot assign "Agent #1" at the protocol level -- numbering would need to happen in Preventra's own state or off-chain. -
ATOM Engine is optional but defaults to enabled. Use
register_with_options(uri, false)to register without ATOM. Once enabled, ATOM cannot be disabled (one-way flag). -
Owner is cached, not live. The
AgentAccount.ownerfield is set at registration and updated viasync_owner()ortransfer_agent(). If someone transfers the Metaplex Core asset externally (outside QuantuLabs), the owner cache becomes stale untilsync_owner()is called. -
Ed25519 signature requirement for wallet. Setting the agent wallet requires a pre-verified Ed25519 signature in the transaction's instruction sysvar. This is a strict security model: the wallet key must prove it controls the private key.
-
Collection pointer format. Must follow
c1:<cid_norm>format, max 128 bytes. First-write-wins with optional lock. -
Metadata uses SHA256 key hashing. Keys are hashed with SHA256 and truncated to 16 bytes for PDA derivation. The full key is stored on-chain for verification. This provides 2^128 collision resistance.
-
Account size is dynamic. AgentAccount uses
InitSpacederive macro, meaning size is calculated from field types. The variable-length String fields (agent_uri, nft_name, col) use Borsh length-prefixed encoding.
Client-side (TypeScript SDK):
1. Generate asset Keypair
2. Call QuantuLabs register() -> creates identity
3. Call Preventra register(asset_pubkey, ...) -> creates vault + provenance
4. SDK wraps both calls in a clean preventra.register({...}) method
VaultAccount PDA: ["vault", agent_identity.as_ref()]
where agent_identity = QuantuLabs asset Pubkey (or mock Pubkey in Steps 3-4)
ProvenanceRecord PDA: ["provenance", agent_identity.as_ref()]
where agent_identity = same as above
When using real QuantuLabs integration (Step 5), Preventra's register() should:
- Accept the asset Pubkey as an argument
- Derive the QuantuLabs AgentAccount PDA:
["agent", asset.as_ref()]using QuantuLabs program ID - Read the AgentAccount to verify: (a) it exists, (b) caller is the owner
- Initialize VaultAccount and ProvenanceRecord using the asset Pubkey
This means Preventra reads QuantuLabs state but does not CPI into it. Clean separation.
All source files examined from /tmp/quantulabs-research/8004-solana/:
programs/agent-registry-8004/src/lib.rs-- Program entry, all instruction definitionsprograms/agent-registry-8004/src/identity/state.rs-- Account structuresprograms/agent-registry-8004/src/identity/instructions.rs-- Instruction implementationsprograms/agent-registry-8004/src/identity/contexts.rs-- Anchor account contextsprograms/agent-registry-8004/src/identity/events.rs-- Event definitionsprograms/agent-registry-8004/src/constants.rs-- PDA seed constantsprograms/agent-registry-8004/src/error.rs-- Error codesprograms/agent-registry-8004/src/core_asset.rs-- Metaplex Core helpersAnchor.toml-- Configuration, program IDs, test validator setuppackage.json-- Dependenciesidl/agent_registry_8004.json-- Full IDL (v0.5.2)idl/atom_engine.json-- ATOM Engine IDL (v0.2.2)types/atom_engine.ts-- TypeScript types for ATOM Enginetests/identity-tests.ts-- Full identity test suitetests/init-localnet.ts-- Initialization testtests/utils/helpers.ts-- PDA derivation helpers, test utilities
QuantuLabs 8004-solana is viable for Preventra identity integration. The devnet program is live and the interface is well-documented through their IDL and test suite.
The critical finding: Single-transaction CPI registration is not straightforward because the Metaplex Core asset must be a fresh Keypair signer. The recommended approach for Step 5 is a two-transaction flow wrapped by the Preventra SDK, where identity creation happens first and Preventra verifies it by reading the QuantuLabs AgentAccount PDA.
The de-risk strategy is validated: Building Steps 3-4 with mock identity is the right call. Core IP (vault governance, build provenance) has zero dependency on QuantuLabs. When we layer in real integration in Step 5, we read QuantuLabs state rather than CPI into it, keeping the integration clean and debuggable.
No blockers identified. Ready to proceed with Step 2 (Anchor scaffold) upon Stratburt approval.