> ## 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.

# React Hooks

> Complete reference for all NullGraph React hooks with usage examples

## Overview

The NullGraph SDK provides 9 React hooks for interacting with the Solana program:

* **Read Hooks**: Fetch on-chain data with automatic updates
* **Write Hooks**: Submit transactions and modify state

## Read Hooks

### useProtocolState

Fetch the global protocol state including counters, fees, and treasury.

```typescript theme={null}
function useProtocolState(): {
  data: ProtocolStateAccount | null;
  loading: boolean;
  error: string | null;
  refetch: () => Promise<void>;
}
```

#### Usage Example

```tsx theme={null}
import { useProtocolState } from '@/hooks/useProtocolState';

function ProtocolStats() {
  const { data, loading, error, refetch } = useProtocolState();

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!data) return null;

  return (
    <div>
      <p>Total NKAs: {data.nkaCounter.toString()}</p>
      <p>Total Bounties: {data.bountyCounter.toString()}</p>
      <p>Fee: {data.feeBasisPoints / 100}%</p>
      <button onClick={refetch}>Refresh</button>
    </div>
  );
}
```

#### Return Fields

<ParamField path="data" type="ProtocolStateAccount | null">
  The protocol state account data, or null if not yet loaded
</ParamField>

<ParamField path="loading" type="boolean">
  True while fetching data from the blockchain
</ParamField>

<ParamField path="error" type="string | null">
  Error message if the fetch failed
</ParamField>

<ParamField path="refetch" type="() => Promise<void>">
  Function to manually refresh the data
</ParamField>

***

### useNullResults

Fetch all null result (NKA) submissions, sorted by specimen number descending.

```typescript theme={null}
function useNullResults(): {
  data: NullResultWithKey[];
  loading: boolean;
  error: string | null;
  refetch: () => Promise<void>;
}
```

#### Usage Example

```tsx theme={null}
import { useNullResults } from '@/hooks/useNullResults';

function NullResultList() {
  const { data, loading, error } = useNullResults();

  if (loading) return <div>Loading null results...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <ul>
      {data.map((result) => (
        <li key={result.publicKey.toString()}>
          NKA-{result.specimenNumber.toString().padStart(4, '0')}
          <br />
          Researcher: {result.researcher.toString()}
          <br />
          P-Value: {result.pValue}
          <br />
          Sample Size: {result.sampleSize}
        </li>
      ))}
    </ul>
  );
}
```

#### Return Fields

<ParamField path="data" type="NullResultWithKey[]">
  Array of null result accounts, each with a `publicKey` field
</ParamField>

<ParamField path="loading" type="boolean">
  True while fetching data
</ParamField>

<ParamField path="error" type="string | null">
  Error message if fetch failed
</ParamField>

<ParamField path="refetch" type="() => Promise<void>">
  Manually refresh the data
</ParamField>

***

### useBounties

Fetch all bounties, sorted by bounty number descending.

```typescript theme={null}
function useBounties(): {
  data: NullBountyWithKey[];
  loading: boolean;
  error: string | null;
  refetch: () => Promise<void>;
}
```

#### Usage Example

```tsx theme={null}
import { useBounties } from '@/hooks/useBounties';
import { BOUNTY_STATUS } from '@/types';

function BountyList() {
  const { data, loading, error } = useBounties();

  if (loading) return <div>Loading bounties...</div>;
  if (error) return <div>Error: {error}</div>;

  const openBounties = data.filter(b => b.status === BOUNTY_STATUS.OPEN);

  return (
    <ul>
      {openBounties.map((bounty) => (
        <li key={bounty.publicKey.toString()}>
          NB-{bounty.bountyNumber.toString().padStart(4, '0')}
          <br />
          Reward: {bounty.rewardAmount.toString()} BIO
          <br />
          Creator: {bounty.creator.toString()}
        </li>
      ))}
    </ul>
  );
}
```

***

### useBountySubmissions

Fetch submissions for a specific bounty, or all submissions if no bounty is specified.

```typescript theme={null}
function useBountySubmissions(bountyKey?: PublicKey): {
  data: BountySubmissionWithKey[];
  loading: boolean;
  error: string | null;
  refetch: () => Promise<void>;
}
```

#### Usage Example

```tsx theme={null}
import { useBountySubmissions } from '@/hooks/useBountySubmissions';
import { PublicKey } from '@solana/web3.js';
import { SUBMISSION_STATUS } from '@/types';

function BountySubmissionList({ bountyKey }: { bountyKey: PublicKey }) {
  const { data, loading, error } = useBountySubmissions(bountyKey);

  if (loading) return <div>Loading submissions...</div>;
  if (error) return <div>Error: {error}</div>;

  const pending = data.filter(s => s.status === SUBMISSION_STATUS.PENDING);

  return (
    <div>
      <h3>Pending Submissions: {pending.length}</h3>
      <ul>
        {pending.map((submission) => (
          <li key={submission.publicKey.toString()}>
            Researcher: {submission.researcher.toString()}
            <br />
            NKA: {submission.nullResult.toString()}
          </li>
        ))}
      </ul>
    </div>
  );
}
```

#### Parameters

<ParamField path="bountyKey" type="PublicKey" optional>
  Public key of the bounty to filter submissions. If omitted, returns all submissions.
</ParamField>

***

## Write Hooks

### useSubmitNullResult

Submit a new null result (NKA) to the protocol.

```typescript theme={null}
interface SubmitNullResultArgs {
  hypothesis: number[];
  methodology: number[];
  expectedOutcome: number[];
  actualOutcome: number[];
  pValue: number;
  sampleSize: number;
  dataHash: number[];
}

function useSubmitNullResult(): {
  submit: (args: SubmitNullResultArgs) => Promise<number | null>;
  loading: boolean;
}
```

#### Usage Example

```tsx theme={null}
import { useSubmitNullResult } from '@/hooks/useSubmitNullResult';
import { useState } from 'react';

function SubmitNullResultForm() {
  const { submit, loading } = useSubmitNullResult();
  const [pValue, setPValue] = useState('');
  const [sampleSize, setSampleSize] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    const args = {
      hypothesis: Buffer.from('H0: No effect observed').toJSON().data,
      methodology: Buffer.from('Double-blind RCT').toJSON().data,
      expectedOutcome: Buffer.from('Significant result').toJSON().data,
      actualOutcome: Buffer.from('No significant difference').toJSON().data,
      pValue: parseFloat(pValue),
      sampleSize: parseInt(sampleSize),
      dataHash: new Array(32).fill(0), // Replace with actual hash
    };

    const specimenNumber = await submit(args);
    if (specimenNumber) {
      console.log(`Submitted NKA-${specimenNumber.toString().padStart(4, '0')}`);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="number"
        step="0.001"
        placeholder="P-Value"
        value={pValue}
        onChange={(e) => setPValue(e.target.value)}
        required
      />
      <input
        type="number"
        placeholder="Sample Size"
        value={sampleSize}
        onChange={(e) => setSampleSize(e.target.value)}
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Submitting...' : 'Submit NKA'}
      </button>
    </form>
  );
}
```

#### Parameters

<ParamField path="hypothesis" type="number[]" required>
  UTF-8 encoded hypothesis string as byte array
</ParamField>

<ParamField path="methodology" type="number[]" required>
  UTF-8 encoded methodology description as byte array
</ParamField>

<ParamField path="expectedOutcome" type="number[]" required>
  UTF-8 encoded expected outcome as byte array
</ParamField>

<ParamField path="actualOutcome" type="number[]" required>
  UTF-8 encoded actual outcome as byte array
</ParamField>

<ParamField path="pValue" type="number" required>
  Statistical p-value (0.0 to 1.0)
</ParamField>

<ParamField path="sampleSize" type="number" required>
  Number of samples in the study
</ParamField>

<ParamField path="dataHash" type="number[]" required>
  32-byte hash of the underlying data
</ParamField>

#### Return Value

Returns the specimen number (e.g., `1`, `2`, `3`) on success, or `null` if the transaction failed.

***

### useCreateBounty

Create a new bounty with BIO token rewards.

```typescript theme={null}
interface CreateBountyArgs {
  description: number[];
  rewardAmount: number; // in BIO (6 decimals)
  deadline: number; // Unix timestamp
}

function useCreateBounty(): {
  create: (args: CreateBountyArgs) => Promise<number | null>;
  loading: boolean;
}
```

#### Usage Example

```tsx theme={null}
import { useCreateBounty } from '@/hooks/useCreateBounty';
import { useState } from 'react';

function CreateBountyForm() {
  const { create, loading } = useCreateBounty();
  const [description, setDescription] = useState('');
  const [rewardAmount, setRewardAmount] = useState('');
  const [deadline, setDeadline] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    const args = {
      description: Buffer.from(description).toJSON().data,
      rewardAmount: parseFloat(rewardAmount) * 1_000_000, // Convert to lamports
      deadline: new Date(deadline).getTime() / 1000, // Convert to Unix timestamp
    };

    const bountyNumber = await create(args);
    if (bountyNumber) {
      console.log(`Created NB-${bountyNumber.toString().padStart(4, '0')}`);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <textarea
        placeholder="Bounty description"
        value={description}
        onChange={(e) => setDescription(e.target.value)}
        required
      />
      <input
        type="number"
        placeholder="Reward (BIO)"
        value={rewardAmount}
        onChange={(e) => setRewardAmount(e.target.value)}
        required
      />
      <input
        type="datetime-local"
        value={deadline}
        onChange={(e) => setDeadline(e.target.value)}
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create Bounty'}
      </button>
    </form>
  );
}
```

<Note>
  The hook automatically handles BIO token transfers from the creator's associated token account to the bounty vault.
</Note>

***

### useSubmitToBounty

Submit an existing null result to a bounty.

```typescript theme={null}
function useSubmitToBounty(): {
  submit: (bountyKey: PublicKey, nullResultKey: PublicKey) => Promise<boolean>;
  loading: boolean;
}
```

#### Usage Example

```tsx theme={null}
import { useSubmitToBounty } from '@/hooks/useSubmitToBounty';
import { PublicKey } from '@solana/web3.js';

function SubmitToBountyButton({
  bountyKey,
  nullResultKey
}: {
  bountyKey: PublicKey;
  nullResultKey: PublicKey;
}) {
  const { submit, loading } = useSubmitToBounty();

  const handleSubmit = async () => {
    const success = await submit(bountyKey, nullResultKey);
    if (success) {
      console.log('Submission created!');
    }
  };

  return (
    <button onClick={handleSubmit} disabled={loading}>
      {loading ? 'Submitting...' : 'Submit to Bounty'}
    </button>
  );
}
```

***

### useApproveBountySubmission

Approve a bounty submission and transfer rewards to the researcher.

```typescript theme={null}
function useApproveBountySubmission(): {
  approve: (
    bounty: NullBountyWithKey,
    submission: BountySubmissionWithKey
  ) => Promise<boolean>;
  loading: boolean;
}
```

#### Usage Example

```tsx theme={null}
import { useApproveBountySubmission } from '@/hooks/useApproveBountySubmission';
import type { NullBountyWithKey, BountySubmissionWithKey } from '@/types';

function ApproveSubmissionButton({
  bounty,
  submission
}: {
  bounty: NullBountyWithKey;
  submission: BountySubmissionWithKey;
}) {
  const { approve, loading } = useApproveBountySubmission();

  const handleApprove = async () => {
    const success = await approve(bounty, submission);
    if (success) {
      console.log('Bounty approved and BIO transferred!');
    }
  };

  return (
    <button onClick={handleApprove} disabled={loading}>
      {loading ? 'Approving...' : 'Approve Submission'}
    </button>
  );
}
```

<Warning>
  This hook automatically creates associated token accounts for the researcher and treasury if they don't exist. The creator pays for account rent.
</Warning>

***

### useCloseBounty

Close an open bounty and refund BIO tokens to the creator.

```typescript theme={null}
function useCloseBounty(): {
  close: (bounty: NullBountyWithKey) => Promise<boolean>;
  loading: boolean;
}
```

#### Usage Example

```tsx theme={null}
import { useCloseBounty } from '@/hooks/useCloseBounty';
import type { NullBountyWithKey } from '@/types';
import { BOUNTY_STATUS } from '@/types';

function CloseBountyButton({ bounty }: { bounty: NullBountyWithKey }) {
  const { close, loading } = useCloseBounty();

  const handleClose = async () => {
    if (bounty.status !== BOUNTY_STATUS.OPEN) {
      alert('Only open bounties can be closed');
      return;
    }

    const success = await close(bounty);
    if (success) {
      console.log('Bounty closed and BIO refunded!');
    }
  };

  return (
    <button onClick={handleClose} disabled={loading}>
      {loading ? 'Closing...' : 'Close Bounty'}
    </button>
  );
}
```

***

## Hook Patterns

### Error Handling

All write hooks display toast notifications automatically. Read hooks return error messages:

```tsx theme={null}
const { data, error } = useProtocolState();

if (error) {
  return <ErrorComponent message={error} />;
}
```

### Loading States

All hooks provide a `loading` boolean for UI feedback:

```tsx theme={null}
const { submit, loading } = useSubmitNullResult();

return (
  <button disabled={loading}>
    {loading ? 'Submitting...' : 'Submit'}
  </button>
);
```

### Refetching Data

Read hooks provide a `refetch` function for manual updates:

```tsx theme={null}
const { data, refetch } = useNullResults();

useEffect(() => {
  // Refetch every 30 seconds
  const interval = setInterval(refetch, 30000);
  return () => clearInterval(interval);
}, [refetch]);
```

## Next Steps

<CardGroup cols={2}>
  <Card title="PDA Derivation" icon="key" href="/sdk/pda-derivation">
    Learn how to derive Program Derived Addresses for accounts
  </Card>

  <Card title="Types" icon="code" href="/sdk/types">
    Explore TypeScript interfaces for type-safe development
  </Card>
</CardGroup>
