Skip to main content

BIO Protocol Integration

NullGraph is natively built for Bio Protocol, aligning with its mission to create decentralized infrastructure for scientific research and data.
All NullGraph bounties are denominated in BIO tokens, directly integrating with Bio Protocol’s token economy and creating economic incentives for publishing negative results.

Why Bio Protocol?

Bio Protocol and NullGraph share a common vision:

Decentralized Science

Both protocols aim to remove centralized gatekeepers from scientific publishing and funding.

Solana Settlement

Built on the same Layer 1 blockchain, enabling seamless composability and shared wallet infrastructure.

Token Alignment

BIO tokens power NullGraph’s bounty economy, creating direct economic utility for Bio Protocol’s ecosystem.

Data Integrity

Both use cryptographic proofs (hashes, signatures) to ensure research data provenance and tamper-resistance.

BIO Token in NullGraph

Bounty Denomination

All bounty rewards are paid in BIO tokens:
pub struct NullBounty {
    pub reward_amount: u64,   // BIO reward in base units (6 decimals)
    pub usdc_mint: Pubkey,    // Token mint address (BIO)
    pub vault: Pubkey,        // Vault holding escrowed BIO
    // ...
}
While the field is named usdc_mint in the current implementation, it refers to the BIO token mint on Solana. This naming will be updated in future versions.

Token Economics

BIO flows through the protocol in three paths:
ActionBIO FlowFee
Create BountyCreator → Vault0%
Approve SubmissionVault → Researcher (97.5%)
Vault → Treasury (2.5%)
2.5%
Close BountyVault → Creator0%

SPL Token Interface

NullGraph uses Anchor’s SPL Token Interface for all BIO token operations:
use anchor_spl::token_interface::{
    Mint, TokenAccount, TokenInterface,
    transfer_checked, TransferChecked,
};

Why Token Interface?

Token-2022 Ready

Compatible with both SPL Token (legacy) and Token-2022 (new standard) without code changes.

Type Safety

InterfaceAccount<TokenAccount> enforces correct account types at compile time.

Future-Proof

Automatically supports new token features (transfer fees, confidential transfers, etc.).

Best Practice

Recommended by Solana Foundation for all new token programs.

Transfer Example

All BIO transfers use transfer_checked:
transfer_checked(
    CpiContext::new(
        ctx.accounts.token_program.to_account_info(),
        TransferChecked {
            from: ctx.accounts.creator_usdc_ata.to_account_info(),
            mint: ctx.accounts.usdc_mint.to_account_info(),
            to: ctx.accounts.vault.to_account_info(),
            authority: ctx.accounts.creator.to_account_info(),
        },
    ),
    reward_amount,  // Amount in base units
    decimals,       // Must match mint (6 for BIO)
)?;
Why transfer_checked?
  • Validates mint — ensures tokens are from the correct BIO mint
  • Validates decimals — prevents precision errors (e.g., treating 6-decimal tokens as 9-decimal)
  • Safer than transfer — explicit validation at instruction level
Using transfer without _checked is considered unsafe in production token programs. Always use transfer_checked.

BIO Token Decimals

BIO tokens use 6 decimals (same as USDC):
1 BIO = 1_000_000 base units
0.5 BIO = 500_000 base units
100 BIO = 100_000_000 base units

Frontend Conversion

const BIO_DECIMALS = 6;

// Human-readable to base units
const bioToBaseUnits = (bio: number): number => {
  return Math.floor(bio * Math.pow(10, BIO_DECIMALS));
};

// Base units to human-readable
const baseUnitsToBio = (raw: number): number => {
  return raw / Math.pow(10, BIO_DECIMALS);
};

// Examples
bioToBaseUnits(10.5)      // 10_500_000
baseUnitsToBio(2_500_000) // 2.5

On-Chain Validation

The program reads decimals directly from the mint account:
let decimals = ctx.accounts.usdc_mint.decimals;  // 6 for BIO

transfer_checked(
    // ...
    reward_amount,
    decimals,  // Must match mint's decimals
)?;
If decimals doesn’t match the mint, the transaction fails — preventing loss of funds.

Associated Token Accounts (ATAs)

What are ATAs?

ATAs are deterministic token accounts derived from a wallet address and token mint:
import { getAssociatedTokenAddressSync } from '@solana/spl-token';
import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';

const BIO_MINT = new PublicKey('BioTokenMintAddress...');
const walletAddress = new PublicKey('UserWalletAddress...');

const ata = getAssociatedTokenAddressSync(
  BIO_MINT,
  walletAddress,
  false,  // allowOwnerOffCurve
  TOKEN_2022_PROGRAM_ID  // Use Token-2022 program
);

console.log('User BIO ATA:', ata.toBase58());

ATA Benefits

Predictable Address

Same wallet + mint always produces the same ATA address.

One Account per Token

Users have exactly one ATA for each token mint they hold.

Easy Lookup

No need to track custom token accounts — derive on-demand.

Standard

Universal across all Solana dApps and wallets.

Creating ATAs

Anchor’s #[account(init, ...)] can automatically create ATAs:
#[account(
    init,
    payer = creator,
    token::mint = usdc_mint,
    token::authority = vault,
    seeds = [b"bounty_vault", bounty.key().as_ref()],
    bump,
)]
pub vault: InterfaceAccount<'info, TokenAccount>,
For user ATAs, use the associated_token constraint:
// Frontend: Ensure user has BIO ATA before transaction
const ensureAta = async (
  connection: Connection,
  payer: Keypair,
  owner: PublicKey,
  mint: PublicKey
) => {
  const ata = getAssociatedTokenAddressSync(
    mint,
    owner,
    false,
    TOKEN_2022_PROGRAM_ID
  );

  const account = await connection.getAccountInfo(ata);
  if (!account) {
    // Create ATA if it doesn't exist
    const tx = new Transaction().add(
      createAssociatedTokenAccountInstruction(
        payer.publicKey,
        ata,
        owner,
        mint,
        TOKEN_2022_PROGRAM_ID
      )
    );
    await sendAndConfirmTransaction(connection, tx, [payer]);
    console.log('Created ATA:', ata.toBase58());
  }

  return ata;
};

Vault Escrow Mechanism

Bounties lock BIO in PDA-controlled vault accounts:
seeds = [b"bounty_vault", bounty_pda_key]

Vault Authority

The vault is its own authority (PDA signer):
#[account(
    init,
    payer = creator,
    token::mint = usdc_mint,
    token::authority = vault,  // Vault signs for itself
    seeds = [b"bounty_vault", bounty.key().as_ref()],
    bump,
)]
pub vault: InterfaceAccount<'info, TokenAccount>,

PDA Signer Seeds

Only the program can sign for the vault using signer seeds:
let bounty_key = bounty.key();
let vault_seeds: &[&[u8]] = &[
    b"bounty_vault",
    bounty_key.as_ref(),
    &[bounty.vault_bump],
];

transfer_checked(
    CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(),
        TransferChecked { /* ... */ },
        &[vault_seeds],  // PDA signer
    ),
    amount,
    decimals,
)?;

Security: No Private Keys

The vault has no private key. Only the program, using the correct PDA seeds, can authorize transfers. This eliminates custodial risk.

Cross-Program Invocation (CPI)

All token operations use CPI to the SPL Token Interface program:
transfer_checked(
    CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(),  // SPL Token Interface
        TransferChecked {
            from: vault,
            mint: bio_mint,
            to: researcher_ata,
            authority: vault,  // PDA signer
        },
        &[vault_seeds],
    ),
    payout_amount,
    6,  // BIO decimals
)?;

CPI Flow Diagram

┌─────────────────────┐
│  NullGraph Program  │
│   (approve_bounty)  │
└──────────┬──────────┘
           │ CPI
           v
┌─────────────────────┐
│ SPL Token Interface │
│  (transfer_checked) │
└──────────┬──────────┘

           v
  ┌─────────────┐     ┌──────────────┐
  │ Vault ATA   │ --> │ Researcher   │
  │ (escrowed)  │     │ ATA          │
  └─────────────┘     └──────────────┘
CPI allows programs to call other programs atomically — all operations succeed or all fail together.

Bio Protocol Alignment

NullGraph advances Bio Protocol’s core mission:

1. Shared Settlement Layer

Both protocols use Solana for:
  • Fast finality (~400ms)
  • Low transaction costs (~$0.00025)
  • High throughput (65,000 TPS)
  • Composable program architecture

2. BIO Token Utility

NullGraph creates real utility for BIO tokens:

Bounty Payments

BioDAOs spend BIO to acquire valuable null results

Researcher Incentives

Researchers earn BIO for publishing negative data

Protocol Fees

Treasury accumulates BIO for ecosystem development

Liquidity Demand

More bounties = more BIO demand = stronger token economics

3. Data Provenance

NullGraph uses SHA-256 hashing (same as Bio Protocol) for data integrity:
pub data_hash: [u8; 32],  // SHA-256 of dataset
This provides the same chain-of-custody guarantees Bio Protocol uses for research data provenance:
  • Tamper-proof — changing one byte invalidates the hash
  • Verifiable — anyone can recompute and verify
  • Permanent — stored on-chain forever
  • Privacy-preserving — hash reveals nothing about data contents

4. BioDAO Integration

BioDAOs can use NullGraph to:
1

Fund Targeted Research

Post bounties for specific null results needed for their research agenda
2

Avoid Redundancy

Browse existing NKAs before funding new experiments
3

Build Knowledge Graphs

Link NKAs to create comprehensive negative result databases
4

Community Verification

Use the status field (Pending/Verified/Disputed) for peer review

Future Integrations

Decentralized Verification

Community-driven NKA validation network with BIO token incentives

Knowledge Graph Indexing

On-chain graph linking NKAs by hypothesis, methodology, and outcomes

BioDAO Governance

DAO-controlled treasury and protocol parameters

Cross-Chain Bridge

Integrate with Bio Protocol’s Ethereum contracts via Wormhole

Code Example: BIO Transfer

Complete example of transferring BIO from vault to researcher:
use anchor_spl::token_interface::{
    transfer_checked, TransferChecked,
};

pub fn payout_researcher(
    vault: &InterfaceAccount<TokenAccount>,
    researcher_ata: &InterfaceAccount<TokenAccount>,
    mint: &InterfaceAccount<Mint>,
    token_program: &Interface<TokenInterface>,
    vault_seeds: &[&[u8]],
    amount: u64,
) -> Result<()> {
    let decimals = mint.decimals;  // 6 for BIO

    transfer_checked(
        CpiContext::new_with_signer(
            token_program.to_account_info(),
            TransferChecked {
                from: vault.to_account_info(),
                mint: mint.to_account_info(),
                to: researcher_ata.to_account_info(),
                authority: vault.to_account_info(),  // Vault is authority
            },
            &[vault_seeds],  // PDA signer
        ),
        amount,
        decimals,
    )?;

    msg!("Transferred {} BIO to researcher", amount as f64 / 1_000_000.0);
    Ok(())
}

Querying BIO Balances

Check user’s BIO balance:
const getBioBalance = async (
  connection: Connection,
  walletAddress: PublicKey,
  bioMint: PublicKey
) => {
  const ata = getAssociatedTokenAddressSync(
    bioMint,
    walletAddress,
    false,
    TOKEN_2022_PROGRAM_ID
  );

  try {
    const balance = await connection.getTokenAccountBalance(ata);
    return parseFloat(balance.value.uiAmount || '0');
  } catch (e) {
    // ATA doesn't exist yet
    return 0;
  }
};

// Usage
const balance = await getBioBalance(
  connection,
  wallet.publicKey,
  BIO_MINT
);
console.log(`You have ${balance} BIO`);

Next Steps

Null Knowledge Assets

Learn how NKAs store scientific data on-chain

Bounty Marketplace

Explore how to create and fulfill BIO-denominated bounties

Build docs developers (and LLMs) love