> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Ge0frey/nullgraph/llms.txt
> Use this file to discover all available pages before exploring further.

# Null Knowledge Assets (NKAs)

> Learn how NullGraph transforms unpublished negative scientific results into permanent on-chain assets with cryptographic data integrity.

# Null Knowledge Assets (NKAs)

Null Knowledge Assets (NKAs) are the foundational primitive of NullGraph. Each NKA is a permanent, on-chain record of a negative scientific result stored as a Solana Program Derived Address (PDA).

<Note>
  **95% of null results never get published.** NKAs solve publication bias by making negative results permanent, discoverable, and economically valuable.
</Note>

## What is an NKA?

An NKA is a structured data account containing:

* **Hypothesis** — the original hypothesis tested
* **Methodology** — experimental methodology summary
* **Expected vs. Actual Outcome** — what was expected and what actually happened
* **Statistical Data** — p-value and sample size
* **Data Hash** — SHA-256 fingerprint of the underlying dataset
* **Specimen ID** — unique sequential identifier (e.g., `NKA-0042`)
* **Researcher** — wallet address of the submitter
* **Status** — verification state (Pending, Verified, Disputed)

## Account Structure

Each NKA is stored in a `NullResult` account on Solana:

```rust theme={null}
pub struct NullResult {
    pub researcher: Pubkey,          // Submitting wallet
    pub specimen_number: u64,        // Sequential ID (NKA-0001, NKA-0002...)
    pub hypothesis: [u8; 128],       // Hypothesis tested (UTF-8, zero-padded)
    pub methodology: [u8; 128],      // Methodology summary
    pub expected_outcome: [u8; 128], // What was expected
    pub actual_outcome: [u8; 128],   // What actually happened
    pub p_value: u32,                // Fixed-point (8700 = 0.8700)
    pub sample_size: u32,            // Sample size n
    pub data_hash: [u8; 32],         // SHA-256 of attached data file
    pub status: u8,                  // 0 = Pending, 1 = Verified, 2 = Disputed
    pub created_at: i64,             // Unix timestamp
    pub bump: u8,                    // PDA bump seed
}
```

<Info>
  All NKA accounts are **program-owned PDAs** derived from:

  ```
  seeds: ["null_result", researcher_pubkey, specimen_number_le_bytes]
  ```
</Info>

## Specimen Identifiers

Each NKA receives a unique sequential identifier from the global `ProtocolState` counter:

* First submission: `NKA-0001`
* Second submission: `NKA-0002`
* And so on...

The counter is auto-incremented atomically during submission:

```rust theme={null}
pub fn submit_null_result(ctx: Context<SubmitNullResult>, ...) -> Result<()> {
    let state = &mut ctx.accounts.protocol_state;
    state.nka_counter += 1;
    let specimen_number = state.nka_counter;
    
    let nr = &mut ctx.accounts.null_result;
    nr.specimen_number = specimen_number;
    // ... store other fields
}
```

<Tip>
  Specimen numbers are **globally unique** and **permanent**. They provide a universal reference system for null results across the entire protocol.
</Tip>

## Data Hashing & Integrity

NKAs use **SHA-256 cryptographic hashing** to link on-chain metadata to off-chain datasets without storing the raw data on-chain.

### How it works

1. **Researcher uploads data file** through the frontend (CSV, Excel, JSON, etc.)
2. **Client-side hashing** via Web Crypto API computes SHA-256 digest
3. **Hash stored on-chain** in the `data_hash` field (32 bytes)
4. **File stays off-chain** (IPFS, Arweave, or researcher's own storage)

```typescript theme={null}
// Frontend hashing example
const hashFile = async (file: File): Promise<Uint8Array> => {
  const buffer = await file.arrayBuffer();
  const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
  return new Uint8Array(hashBuffer);
};
```

### Verification Flow

<CardGroup cols={2}>
  <Card title="Original Submission" icon="upload">
    Researcher uploads `experiment.csv` → SHA-256: `a3c8f...91b2`
  </Card>

  <Card title="Later Verification" icon="check">
    Anyone can download the file and verify: `sha256(experiment.csv) == a3c8f...91b2`
  </Card>
</CardGroup>

<Note>
  The data hash provides a **tamper-proof link** between the on-chain record and the underlying dataset. If even one byte changes, the hash will not match.
</Note>

## Metadata Structure

All text fields use **fixed-size byte arrays** with zero-padding:

| Field              | Size      | Format             |
| ------------------ | --------- | ------------------ |
| `hypothesis`       | 128 bytes | UTF-8, zero-padded |
| `methodology`      | 128 bytes | UTF-8, zero-padded |
| `expected_outcome` | 128 bytes | UTF-8, zero-padded |
| `actual_outcome`   | 128 bytes | UTF-8, zero-padded |

### Encoding Example

```typescript theme={null}
// Convert string to fixed-size byte array
const encodeString = (str: string, size: number): Uint8Array => {
  const buffer = new Uint8Array(size);
  const bytes = new TextEncoder().encode(str.slice(0, size));
  buffer.set(bytes);
  return buffer;
};

const hypothesis = encodeString(
  "Caffeine improves long-term memory consolidation",
  128
);
```

### Decoding Example

```typescript theme={null}
// Read from on-chain account
const decodeString = (bytes: Uint8Array): string => {
  const nullIndex = bytes.indexOf(0);
  const trimmed = nullIndex === -1 ? bytes : bytes.slice(0, nullIndex);
  return new TextDecoder().decode(trimmed);
};

const hypothesis = decodeString(nullResult.hypothesis);
// "Caffeine improves long-term memory consolidation"
```

## Statistical Fields

### P-Value (Fixed-Point)

Stored as a **4-byte unsigned integer** using fixed-point representation:

```
p_value = 8700  →  0.8700
p_value = 5000  →  0.5000
p_value = 123   →  0.0123
```

**Conversion:**

```typescript theme={null}
const pValueToU32 = (p: number): number => Math.floor(p * 10000);
const u32ToPValue = (raw: number): number => raw / 10000;

// Example
pValueToU32(0.8700)  // 8700
u32ToPValue(8700)    // 0.8700
```

### Sample Size

Stored as a **4-byte unsigned integer** (supports up to 4,294,967,295 samples):

```rust theme={null}
pub sample_size: u32,  // Direct integer, no conversion needed
```

## Status Field

NKAs have three possible states:

| Value | Status       | Description                                |
| ----- | ------------ | ------------------------------------------ |
| `0`   | **Pending**  | Newly submitted, awaiting community review |
| `1`   | **Verified** | Validated by community or DAO              |
| `2`   | **Disputed** | Flagged for review or concerns             |

<Info>
  The status field is designed for **future decentralized verification networks**. Currently all NKAs start in Pending state.
</Info>

## Submission Flow

<Steps>
  <Step title="Complete Guided Form">
    Four-step form collects hypothesis, methodology, outcomes, and data file
  </Step>

  <Step title="Client-Side Hashing">
    Browser computes SHA-256 hash of data file (never uploads raw data)
  </Step>

  <Step title="Transaction Build">
    Frontend encodes all fields into fixed-size byte arrays
  </Step>

  <Step title="Wallet Signature">
    User signs transaction in Phantom wallet
  </Step>

  <Step title="On-Chain Execution">
    Program creates PDA, increments counter, emits `NullResultSubmitted` event
  </Step>

  <Step title="Instant Discovery">
    NKA appears on Dashboard with specimen ID (e.g., NKA-0042)
  </Step>
</Steps>

## Code Example: Submit NKA

```typescript theme={null}
import { useAnchorWallet } from '@solana/wallet-adapter-react';
import { Program } from '@coral-xyz/anchor';
import { PublicKey } from '@solana/web3.js';

const submitNullResult = async (
  program: Program,
  hypothesis: string,
  methodology: string,
  expectedOutcome: string,
  actualOutcome: string,
  pValue: number,
  sampleSize: number,
  dataHash: Uint8Array
) => {
  const wallet = useAnchorWallet();
  if (!wallet) throw new Error('Wallet not connected');

  // Derive PDAs
  const [protocolState] = PublicKey.findProgramAddressSync(
    [Buffer.from('protocol_state')],
    program.programId
  );

  const state = await program.account.protocolState.fetch(protocolState);
  const nextSpecimenNumber = state.nkaCounter.toNumber() + 1;

  const [nullResult] = PublicKey.findProgramAddressSync(
    [
      Buffer.from('null_result'),
      wallet.publicKey.toBuffer(),
      Buffer.from(new BigUint64Array([BigInt(nextSpecimenNumber)]).buffer),
    ],
    program.programId
  );

  // Encode fields
  const encodeString = (str: string, size: number) => {
    const buffer = new Uint8Array(size);
    buffer.set(new TextEncoder().encode(str.slice(0, size)));
    return Array.from(buffer);
  };

  // Submit transaction
  const tx = await program.methods
    .submitNullResult(
      encodeString(hypothesis, 128),
      encodeString(methodology, 128),
      encodeString(expectedOutcome, 128),
      encodeString(actualOutcome, 128),
      Math.floor(pValue * 10000),  // Fixed-point conversion
      sampleSize,
      Array.from(dataHash)
    )
    .accounts({
      researcher: wallet.publicKey,
      protocolState,
      nullResult,
    })
    .rpc();

  console.log(`NKA created: NKA-${String(nextSpecimenNumber).padStart(4, '0')}`);
  console.log(`Transaction: ${tx}`);
};
```

## Event Emission

Every NKA submission emits an on-chain event:

```rust theme={null}
#[event]
pub struct NullResultSubmitted {
    pub specimen_number: u64,
    pub researcher: Pubkey,
}
```

This enables **real-time indexing** and **notification systems** for new null results.

## Storage Cost

Each NKA account requires rent-exempt storage:

```
Account discriminator: 8 bytes
NullResult data: 595 bytes (32+8+128+128+128+128+4+4+32+1+8+1)
────────────────────────────
Total: 603 bytes
```

**Rent cost:** \~0.0043 SOL per NKA (paid once by researcher)

<Tip>
  Unlike traditional databases, this storage is **permanent and censorship-resistant**. Once created, an NKA exists forever on Solana.
</Tip>

## Browse & Query

Fetch all NKAs from the program:

```typescript theme={null}
const nullResults = await program.account.nullResult.all();

console.log(`Total NKAs: ${nullResults.length}`);

nullResults.forEach(({ publicKey, account }) => {
  const specimenId = `NKA-${String(account.specimenNumber).padStart(4, '0')}`;
  const hypothesis = decodeString(account.hypothesis);
  console.log(`${specimenId}: ${hypothesis}`);
});
```

## Security Model

<Card title="Ownership & Permissions" icon="shield">
  * Only the **researcher's wallet** can create NKAs under their identity
  * PDA seeds include `researcher.key()` to prevent impersonation
  * All accounts are **program-owned PDAs** (cannot be modified after creation)
  * Immutability ensures **permanent record integrity**
</Card>

## Next Steps

<CardGroup cols={2}>
  <Card title="Bounty Marketplace" href="/features/bounty-marketplace" icon="dollar-sign">
    Learn how to monetize your NKAs through bounties
  </Card>

  <Card title="Protocol Fees" href="/features/protocol-fees" icon="percent">
    Understand the 2.5% fee model and treasury distribution
  </Card>
</CardGroup>
