Skip to main content

Overview

The NullGraph SDK provides fully typed TypeScript interfaces for all on-chain accounts, ensuring type safety when building frontend applications.

Account Interfaces

All account types are exported from types/index.ts:
import type { PublicKey } from '@solana/web3.js';
import type { BN } from '@coral-xyz/anchor';

ProtocolStateAccount

The global protocol state containing counters, fees, and treasury information.
interface ProtocolStateAccount {
  authority: PublicKey;
  nkaCounter: BN;
  bountyCounter: BN;
  feeBasisPoints: number;
  treasury: PublicKey;
  bump: number;
}

Fields

authority
PublicKey
The protocol authority that can update fees and treasury
nkaCounter
BN
Total number of null results submitted. Next submission will be nkaCounter + 1
bountyCounter
BN
Total number of bounties created. Next bounty will be bountyCounter + 1
feeBasisPoints
number
Protocol fee in basis points (e.g., 250 = 2.5%). Applied when bounties are fulfilled.
treasury
PublicKey
Treasury address that receives protocol fees
bump
number
PDA bump seed for the protocol state account

Usage Example

import { useProtocolState } from '@/hooks/useProtocolState';
import type { ProtocolStateAccount } from '@/types';

function ProtocolInfo() {
  const { data } = useProtocolState();

  if (!data) return null;

  const feePercent = data.feeBasisPoints / 100;

  return (
    <div>
      <p>Total NKAs: {data.nkaCounter.toString()}</p>
      <p>Total Bounties: {data.bountyCounter.toString()}</p>
      <p>Protocol Fee: {feePercent}%</p>
      <p>Treasury: {data.treasury.toString()}</p>
    </div>
  );
}

NullResultAccount

A null result (NKA) submission containing research metadata and statistical information.
interface NullResultAccount {
  researcher: PublicKey;
  specimenNumber: BN;
  hypothesis: number[];
  methodology: number[];
  expectedOutcome: number[];
  actualOutcome: number[];
  pValue: number;
  sampleSize: number;
  dataHash: number[];
  status: number;
  createdAt: BN;
  bump: number;
}

Fields

researcher
PublicKey
Public key of the researcher who submitted this null result
specimenNumber
BN
Unique specimen number (e.g., 1 for NKA-0001)
hypothesis
number[]
UTF-8 encoded hypothesis string as byte array (max 256 bytes)
methodology
number[]
UTF-8 encoded methodology description as byte array (max 512 bytes)
expectedOutcome
number[]
UTF-8 encoded expected outcome as byte array (max 256 bytes)
actualOutcome
number[]
UTF-8 encoded actual outcome as byte array (max 256 bytes)
pValue
number
Statistical p-value (0.0 to 1.0) indicating significance
sampleSize
number
Number of samples in the study
dataHash
number[]
32-byte hash of the underlying research data for verification
status
number
Current status: 0 = Pending, 1 = Verified, 2 = Disputed
createdAt
BN
Unix timestamp when the null result was submitted
bump
number
PDA bump seed

Usage Example

import { useNullResults } from '@/hooks/useNullResults';
import { NKA_STATUS } from '@/types';

function NullResultCard({ result }: { result: NullResultAccount }) {
  const hypothesis = Buffer.from(result.hypothesis).toString('utf-8');
  const methodology = Buffer.from(result.methodology).toString('utf-8');
  const actualOutcome = Buffer.from(result.actualOutcome).toString('utf-8');

  const statusLabel = result.status === NKA_STATUS.PENDING
    ? 'Pending'
    : result.status === NKA_STATUS.VERIFIED
    ? 'Verified'
    : 'Disputed';

  return (
    <div>
      <h3>NKA-{result.specimenNumber.toString().padStart(4, '0')}</h3>
      <p><strong>Status:</strong> {statusLabel}</p>
      <p><strong>Hypothesis:</strong> {hypothesis}</p>
      <p><strong>Methodology:</strong> {methodology}</p>
      <p><strong>Actual Outcome:</strong> {actualOutcome}</p>
      <p><strong>P-Value:</strong> {result.pValue.toFixed(4)}</p>
      <p><strong>Sample Size:</strong> {result.sampleSize}</p>
    </div>
  );
}

NullBountyAccount

A bounty offering rewards for matching null results.
interface NullBountyAccount {
  creator: PublicKey;
  bountyNumber: BN;
  description: number[];
  rewardAmount: BN;
  usdcMint: PublicKey;
  vault: PublicKey;
  deadline: BN;
  status: number;
  matchedSubmission: PublicKey;
  createdAt: BN;
  vaultBump: number;
  bump: number;
}

Fields

creator
PublicKey
Public key of the bounty creator
bountyNumber
BN
Unique bounty number (e.g., 1 for NB-0001)
description
number[]
UTF-8 encoded bounty description as byte array (max 512 bytes)
rewardAmount
BN
Reward amount in token lamports (BIO has 6 decimals)
usdcMint
PublicKey
Mint address of the reward token (typically BIO token)
vault
PublicKey
Token account holding the bounty reward
deadline
BN
Unix timestamp when the bounty expires
status
number
Current status: 0 = Open, 1 = Matched, 2 = Fulfilled, 3 = Closed
matchedSubmission
PublicKey
Public key of the approved submission (if status is Matched or Fulfilled)
createdAt
BN
Unix timestamp when the bounty was created
vaultBump
number
PDA bump seed for the vault account
bump
number
PDA bump seed for the bounty account

Usage Example

import { useBounties } from '@/hooks/useBounties';
import { BOUNTY_STATUS } from '@/types';

function BountyCard({ bounty }: { bounty: NullBountyAccount }) {
  const description = Buffer.from(bounty.description).toString('utf-8');
  const rewardBIO = bounty.rewardAmount.toNumber() / 1_000_000; // Convert from lamports
  const deadlineDate = new Date(bounty.deadline.toNumber() * 1000);

  const statusLabel = bounty.status === BOUNTY_STATUS.OPEN
    ? 'Open'
    : bounty.status === BOUNTY_STATUS.MATCHED
    ? 'Matched'
    : bounty.status === BOUNTY_STATUS.FULFILLED
    ? 'Fulfilled'
    : 'Closed';

  const isExpired = Date.now() > deadlineDate.getTime();

  return (
    <div>
      <h3>NB-{bounty.bountyNumber.toString().padStart(4, '0')}</h3>
      <p><strong>Status:</strong> {statusLabel}</p>
      <p><strong>Reward:</strong> {rewardBIO} BIO</p>
      <p><strong>Description:</strong> {description}</p>
      <p><strong>Deadline:</strong> {deadlineDate.toLocaleDateString()}</p>
      {isExpired && <p style={{ color: 'red' }}>Expired</p>}
    </div>
  );
}

BountySubmissionAccount

A submission linking a null result to a bounty.
interface BountySubmissionAccount {
  researcher: PublicKey;
  nullResult: PublicKey;
  bounty: PublicKey;
  status: number;
  createdAt: BN;
  bump: number;
}

Fields

researcher
PublicKey
Public key of the researcher who submitted
nullResult
PublicKey
Public key of the null result being submitted
bounty
PublicKey
Public key of the bounty
status
number
Submission status: 0 = Pending, 1 = Approved, 2 = Rejected
createdAt
BN
Unix timestamp when submission was created
bump
number
PDA bump seed

Usage Example

import { useBountySubmissions } from '@/hooks/useBountySubmissions';
import { SUBMISSION_STATUS } from '@/types';
import type { PublicKey } from '@solana/web3.js';

function SubmissionList({ bountyKey }: { bountyKey: PublicKey }) {
  const { data: submissions } = useBountySubmissions(bountyKey);

  const pending = submissions.filter(s => s.status === SUBMISSION_STATUS.PENDING);
  const approved = submissions.filter(s => s.status === SUBMISSION_STATUS.APPROVED);
  const rejected = submissions.filter(s => s.status === SUBMISSION_STATUS.REJECTED);

  return (
    <div>
      <h4>Pending: {pending.length}</h4>
      <h4>Approved: {approved.length}</h4>
      <h4>Rejected: {rejected.length}</h4>
    </div>
  );
}

Extended Types with Public Keys

For convenience, the SDK provides extended interfaces that include the account’s public key:

NullResultWithKey

interface NullResultWithKey extends NullResultAccount {
  publicKey: PublicKey;
}
Returned by useNullResults() hook.

NullBountyWithKey

interface NullBountyWithKey extends NullBountyAccount {
  publicKey: PublicKey;
}
Returned by useBounties() hook.

BountySubmissionWithKey

interface BountySubmissionWithKey extends BountySubmissionAccount {
  publicKey: PublicKey;
}
Returned by useBountySubmissions() hook.

Status Enums

NKA_STATUS

Status values for null results:
export const NKA_STATUS = {
  PENDING: 0,
  VERIFIED: 1,
  DISPUTED: 2,
} as const;

Usage Example

import { NKA_STATUS } from '@/types';
import type { NullResultAccount } from '@/types';

function getStatusLabel(result: NullResultAccount): string {
  switch (result.status) {
    case NKA_STATUS.PENDING:
      return 'Pending Review';
    case NKA_STATUS.VERIFIED:
      return 'Verified';
    case NKA_STATUS.DISPUTED:
      return 'Under Dispute';
    default:
      return 'Unknown';
  }
}

BOUNTY_STATUS

Status values for bounties:
export const BOUNTY_STATUS = {
  OPEN: 0,
  MATCHED: 1,
  FULFILLED: 2,
  CLOSED: 3,
} as const;

Status Flow

  1. OPEN (0): Bounty is accepting submissions
  2. MATCHED (1): Creator approved a submission but hasn’t transferred funds yet
  3. FULFILLED (2): Reward has been paid out to researcher
  4. CLOSED (3): Bounty was closed by creator without fulfillment

Usage Example

import { BOUNTY_STATUS } from '@/types';
import type { NullBountyAccount } from '@/types';

function canSubmit(bounty: NullBountyAccount): boolean {
  return bounty.status === BOUNTY_STATUS.OPEN;
}

function canClose(bounty: NullBountyAccount): boolean {
  return bounty.status === BOUNTY_STATUS.OPEN;
}

SUBMISSION_STATUS

Status values for bounty submissions:
export const SUBMISSION_STATUS = {
  PENDING: 0,
  APPROVED: 1,
  REJECTED: 2,
} as const;

Usage Example

import { SUBMISSION_STATUS } from '@/types';
import type { BountySubmissionAccount } from '@/types';

function getSubmissionColor(submission: BountySubmissionAccount): string {
  switch (submission.status) {
    case SUBMISSION_STATUS.PENDING:
      return 'yellow';
    case SUBMISSION_STATUS.APPROVED:
      return 'green';
    case SUBMISSION_STATUS.REJECTED:
      return 'red';
    default:
      return 'gray';
  }
}

Type Guards

Create type guards for runtime type checking:
import type { NullResultWithKey, NullBountyWithKey } from '@/types';

function isNullResult(account: unknown): account is NullResultWithKey {
  return (
    typeof account === 'object' &&
    account !== null &&
    'specimenNumber' in account &&
    'researcher' in account &&
    'pValue' in account
  );
}

function isNullBounty(account: unknown): account is NullBountyWithKey {
  return (
    typeof account === 'object' &&
    account !== null &&
    'bountyNumber' in account &&
    'creator' in account &&
    'rewardAmount' in account
  );
}

Utility Functions

Converting Byte Arrays to Strings

function bytesToString(bytes: number[]): string {
  // Remove trailing zeros
  const trimmed = bytes.filter(b => b !== 0);
  return Buffer.from(trimmed).toString('utf-8');
}

// Usage
const hypothesis = bytesToString(nullResult.hypothesis);

Converting Strings to Byte Arrays

function stringToBytes(str: string, maxLength: number): number[] {
  const buffer = Buffer.from(str, 'utf-8');
  if (buffer.length > maxLength) {
    throw new Error(`String exceeds max length of ${maxLength} bytes`);
  }
  return buffer.toJSON().data;
}

// Usage
const hypothesis = stringToBytes('My hypothesis', 256);

Formatting Timestamps

import type { BN } from '@coral-xyz/anchor';

function formatTimestamp(timestamp: BN): string {
  const date = new Date(timestamp.toNumber() * 1000);
  return date.toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  });
}

// Usage
const createdDate = formatTimestamp(nullResult.createdAt);

Formatting Token Amounts

function formatBIO(lamports: BN): string {
  const bio = lamports.toNumber() / 1_000_000;
  return `${bio.toFixed(2)} BIO`;
}

// Usage
const reward = formatBIO(bounty.rewardAmount);

Type Safety Best Practices

Always use the typed interfaces when working with account data. This prevents runtime errors from typos or incorrect field access.
When converting byte arrays to strings, always check for trailing zeros and handle empty arrays gracefully.
Use BN.toNumber() carefully with large numbers. For display purposes, consider using BN.toString() instead to avoid precision loss.

Complete Type Import

Import all types in one statement:
import type {
  ProtocolStateAccount,
  NullResultAccount,
  NullBountyAccount,
  BountySubmissionAccount,
  NullResultWithKey,
  NullBountyWithKey,
  BountySubmissionWithKey,
} from '@/types';

import {
  NKA_STATUS,
  BOUNTY_STATUS,
  SUBMISSION_STATUS,
} from '@/types';

Next Steps

Hooks

Learn how to fetch and manipulate these types with React hooks

PDA Derivation

Understand how to derive addresses for these accounts

Build docs developers (and LLMs) love