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

# Instructions

> Complete reference for all 6 NullGraph program instructions

The NullGraph program exposes 6 public instructions for protocol initialization, NKA submission, bounty lifecycle management, and payouts.

## initialize\_protocol

One-time setup creating the ProtocolState singleton with initial configuration.

**Location**: `lib.rs:18-35`

### Parameters

<ParamField path="fee_basis_points" type="u16" required>
  Protocol fee rate in basis points (1/100th of 1%). Must be between 0-10000.

  **Common values**: `250` (2.5%), `100` (1%), `500` (5%)
</ParamField>

### Accounts

<ParamField path="authority" type="Signer (mut)" required>
  Protocol admin wallet. Becomes the authority stored in ProtocolState. Pays for account creation.
</ParamField>

<ParamField path="protocol_state" type="Account<ProtocolState> (init)" required>
  The ProtocolState singleton PDA being initialized. Seeds: `["protocol_state"]`
</ParamField>

<ParamField path="treasury" type="UncheckedAccount" required>
  Treasury wallet address for fee collection. Not validated on-chain; admin must ensure it has a BIO token account.
</ParamField>

<ParamField path="system_program" type="Program<System>" required>
  Solana System Program for account creation.
</ParamField>

### Behavior

1. Creates the ProtocolState PDA with space `8 + ProtocolState::INIT_SPACE`
2. Sets `authority` to the signer's pubkey
3. Initializes both counters (`nka_counter`, `bounty_counter`) to 0
4. Stores the `fee_basis_points` parameter
5. Stores the `treasury` pubkey
6. Saves the PDA bump seed
7. Emits `ProtocolInitialized` event

### Code Reference

```rust theme={null}
pub fn initialize_protocol(
    ctx: Context<InitializeProtocol>,
    fee_basis_points: u16,
) -> Result<()> {
    let state = &mut ctx.accounts.protocol_state;
    state.authority = ctx.accounts.authority.key();
    state.nka_counter = 0;
    state.bounty_counter = 0;
    state.fee_basis_points = fee_basis_points;
    state.treasury = ctx.accounts.treasury.key();
    state.bump = ctx.bumps.protocol_state;

    emit!(ProtocolInitialized {
        authority: state.authority,
        fee_basis_points,
    });
    Ok(())
}
```

<Warning>
  This instruction can only be called once per program deployment. Attempting to call it again will fail with an "Account already exists" error from Anchor.
</Warning>

***

## submit\_null\_result

Submits a new Null Knowledge Asset (NKA) to the protocol. Any wallet can call this.

**Location**: `lib.rs:37-70`

### Parameters

<ParamField path="hypothesis" type="[u8; 128]" required>
  UTF-8 encoded hypothesis tested. Maximum 128 bytes. Pad with zeros if shorter.
</ParamField>

<ParamField path="methodology" type="[u8; 128]" required>
  UTF-8 encoded methodology summary. Maximum 128 bytes. Pad with zeros if shorter.
</ParamField>

<ParamField path="expected_outcome" type="[u8; 128]" required>
  UTF-8 encoded expected outcome description. Maximum 128 bytes. Pad with zeros if shorter.
</ParamField>

<ParamField path="actual_outcome" type="[u8; 128]" required>
  UTF-8 encoded actual outcome description. Maximum 128 bytes. Pad with zeros if shorter.
</ParamField>

<ParamField path="p_value" type="u32" required>
  Statistical p-value as fixed-point integer (multiply by 10000). Example: `4200` = 0.42
</ParamField>

<ParamField path="sample_size" type="u32" required>
  Number of subjects or observations in the experiment.
</ParamField>

<ParamField path="data_hash" type="[u8; 32]" required>
  SHA-256 hash of the underlying dataset. Can be all zeros if no file attached.
</ParamField>

### Accounts

<ParamField path="researcher" type="Signer (mut)" required>
  The researcher submitting the NKA. Pays for NullResult PDA creation.
</ParamField>

<ParamField path="protocol_state" type="Account<ProtocolState> (mut)" required>
  The protocol state singleton. `nka_counter` will be incremented.
</ParamField>

<ParamField path="null_result" type="Account<NullResult> (init)" required>
  The NullResult PDA being created. Seeds: `["null_result", researcher.key(), &(nka_counter + 1).to_le_bytes()]`
</ParamField>

<ParamField path="system_program" type="Program<System>" required>
  Solana System Program for account creation.
</ParamField>

### Behavior

1. Increments `protocol_state.nka_counter` by 1
2. Assigns the new counter value as `specimen_number`
3. Creates the NullResult PDA with all provided fields
4. Sets `status` to 0 (Pending)
5. Records the current timestamp via `Clock::get()?.unix_timestamp`
6. Saves the PDA bump seed
7. Emits `NullResultSubmitted` event with `specimen_number` and `researcher`

### Code Reference

```rust theme={null}
pub fn submit_null_result(
    ctx: Context<SubmitNullResult>,
    hypothesis: [u8; 128],
    methodology: [u8; 128],
    expected_outcome: [u8; 128],
    actual_outcome: [u8; 128],
    p_value: u32,
    sample_size: u32,
    data_hash: [u8; 32],
) -> 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.researcher = ctx.accounts.researcher.key();
    nr.specimen_number = specimen_number;
    nr.hypothesis = hypothesis;
    nr.methodology = methodology;
    nr.expected_outcome = expected_outcome;
    nr.actual_outcome = actual_outcome;
    nr.p_value = p_value;
    nr.sample_size = sample_size;
    nr.data_hash = data_hash;
    nr.status = 0; // Pending
    nr.created_at = Clock::get()?.unix_timestamp;
    nr.bump = ctx.bumps.null_result;

    emit!(NullResultSubmitted {
        specimen_number,
        researcher: nr.researcher,
    });
    Ok(())
}
```

***

## create\_bounty

Creates a new bounty and escrows BIO tokens into a PDA-controlled vault.

**Location**: `lib.rs:72-121`

### Parameters

<ParamField path="description" type="[u8; 256]" required>
  UTF-8 encoded description of the desired null result. Maximum 256 bytes. Pad with zeros if shorter.
</ParamField>

<ParamField path="reward_amount" type="u64" required>
  BIO token reward in base units (6 decimals). Must be > 0 or instruction fails with `InvalidRewardAmount`.

  **Example**: `1000000` = 1 BIO
</ParamField>

<ParamField path="deadline" type="i64" required>
  Unix timestamp deadline for submissions. Currently not enforced by program logic.
</ParamField>

### Accounts

<ParamField path="creator" type="Signer (mut)" required>
  Bounty creator wallet. Pays for PDA creation and must approve BIO token transfer.
</ParamField>

<ParamField path="protocol_state" type="Account<ProtocolState> (mut)" required>
  Protocol state singleton. `bounty_counter` will be incremented.
</ParamField>

<ParamField path="bounty" type="Account<NullBounty> (init)" required>
  The NullBounty PDA being created. Seeds: `["null_bounty", creator.key(), &(bounty_counter + 1).to_le_bytes()]`
</ParamField>

<ParamField path="vault" type="InterfaceAccount<TokenAccount> (init)" required>
  The vault token account PDA for escrowed BIO tokens. Seeds: `["bounty_vault", bounty.key()]`. Authority is the vault itself.
</ParamField>

<ParamField path="creator_usdc_ata" type="InterfaceAccount<TokenAccount> (mut)" required>
  Creator's BIO token associated token account. Must have sufficient balance for `reward_amount`. Field name is `creator_usdc_ata` in code but holds BIO tokens.
</ParamField>

<ParamField path="usdc_mint" type="InterfaceAccount<Mint>" required>
  BIO token mint address. Field name is `usdc_mint` in code but refers to the BIO mint. Used to validate the transfer and derive decimals (6).
</ParamField>

<ParamField path="token_program" type="Interface<TokenInterface>" required>
  SPL Token program for CPI transfer.
</ParamField>

<ParamField path="associated_token_program" type="Program<AssociatedToken>" required>
  Associated Token program for vault creation.
</ParamField>

<ParamField path="system_program" type="Program<System>" required>
  System program for account creation.
</ParamField>

### Validation

* `reward_amount > 0` (line 78) → else returns `NullGraphError::InvalidRewardAmount`

### Behavior

1. Validates `reward_amount > 0`
2. Increments `protocol_state.bounty_counter` by 1
3. Assigns the new counter value as `bounty_number`
4. Creates the NullBounty PDA with all provided fields
5. Initializes the vault token account with authority set to the vault itself
6. Executes `transfer_checked` CPI transferring `reward_amount` BIO tokens from creator's ATA to vault
7. Sets bounty `status` to 0 (Open) and `matched_submission` to `Pubkey::default()`
8. Records timestamp and saves bump seeds
9. Emits `BountyCreated` event

### Code Reference

```rust theme={null}
pub fn create_bounty(
    ctx: Context<CreateBounty>,
    description: [u8; 256],
    reward_amount: u64,
    deadline: i64,
) -> Result<()> {
    require!(reward_amount > 0, NullGraphError::InvalidRewardAmount);

    let state = &mut ctx.accounts.protocol_state;
    state.bounty_counter += 1;
    let bounty_number = state.bounty_counter;

    let bounty = &mut ctx.accounts.bounty;
    bounty.creator = ctx.accounts.creator.key();
    bounty.bounty_number = bounty_number;
    bounty.description = description;
    bounty.reward_amount = reward_amount;
    bounty.usdc_mint = ctx.accounts.usdc_mint.key();
    bounty.vault = ctx.accounts.vault.key();
    bounty.deadline = deadline;
    bounty.status = 0; // Open
    bounty.matched_submission = Pubkey::default();
    bounty.created_at = Clock::get()?.unix_timestamp;
    bounty.vault_bump = ctx.bumps.vault;
    bounty.bump = ctx.bumps.bounty;

    // Transfer USDC from creator to vault
    let decimals = ctx.accounts.usdc_mint.decimals;
    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,
        decimals,
    )?;

    emit!(BountyCreated {
        bounty_number,
        creator: bounty.creator,
        reward_amount,
        deadline,
    });
    Ok(())
}
```

***

## submit\_to\_bounty

Links a researcher's existing NKA to an open bounty, transitioning the bounty to Matched status.

**Location**: `lib.rs:123-153`

### Parameters

None. The instruction derives all data from the provided accounts.

### Accounts

<ParamField path="researcher" type="Signer (mut)" required>
  The researcher submitting their NKA. Must be the owner of the `null_result` account. Pays for BountySubmission PDA creation.
</ParamField>

<ParamField path="null_result" type="Account<NullResult>" required>
  The NullResult PDA being submitted. Must satisfy `has_one = researcher` constraint.
</ParamField>

<ParamField path="bounty" type="Account<NullBounty> (mut)" required>
  The target bounty. Must have status 0 (Open).
</ParamField>

<ParamField path="submission" type="Account<BountySubmission> (init)" required>
  The BountySubmission PDA being created. Seeds: `["bounty_submission", bounty.key(), null_result.key()]`
</ParamField>

<ParamField path="system_program" type="Program<System>" required>
  System program for account creation.
</ParamField>

### Validation

* `bounty.status == 0` (line 125) → else returns `NullGraphError::InvalidBountyStatus`
* `null_result.researcher == researcher.key()` (enforced by Anchor `has_one` constraint)

### Behavior

1. Validates bounty status is 0 (Open)
2. Creates the BountySubmission PDA
3. Sets `submission.researcher`, `submission.null_result`, `submission.bounty`
4. Sets `submission.status` to 0 (Pending)
5. Records timestamp
6. Updates `bounty.status` to 1 (Matched)
7. Sets `bounty.matched_submission` to the new submission PDA's address
8. Emits `BountySubmissionCreated` event

### Code Reference

```rust theme={null}
pub fn submit_to_bounty(ctx: Context<SubmitToBounty>) -> Result<()> {
    let bounty_status = ctx.accounts.bounty.status;
    require!(bounty_status == 0, NullGraphError::InvalidBountyStatus); // Must be Open

    let researcher_key = ctx.accounts.researcher.key();
    let null_result_key = ctx.accounts.null_result.key();
    let bounty_key = ctx.accounts.bounty.key();
    let specimen_number = ctx.accounts.null_result.specimen_number;
    let now = Clock::get()?.unix_timestamp;

    let submission = &mut ctx.accounts.submission;
    submission.researcher = researcher_key;
    submission.null_result = null_result_key;
    submission.bounty = bounty_key;
    submission.status = 0; // Pending
    submission.created_at = now;
    submission.bump = ctx.bumps.submission;

    let submission_key = submission.key();

    let bounty = &mut ctx.accounts.bounty;
    bounty.status = 1; // Matched
    bounty.matched_submission = submission_key;

    emit!(BountySubmissionCreated {
        bounty_number: bounty.bounty_number,
        specimen_number,
        researcher: researcher_key,
    });
    Ok(())
}
```

<Info>
  The PDA seeds ensure each NKA can only be submitted to a given bounty once. Attempting to submit the same NKA to the same bounty twice will fail with "Account already exists".
</Info>

***

## approve\_bounty\_submission

Bounty creator approves a submission, triggering automatic payout minus protocol fee.

**Location**: `lib.rs:155-229`

### Parameters

None. All data derived from accounts.

### Accounts

<ParamField path="creator" type="Signer (mut)" required>
  Bounty creator. Must match `bounty.creator`.
</ParamField>

<ParamField path="bounty" type="Box<Account<NullBounty>> (mut)" required>
  The bounty being fulfilled. Must satisfy `has_one = creator` constraint. Must have status 1 (Matched).
</ParamField>

<ParamField path="submission" type="Box<Account<BountySubmission>> (mut)" required>
  The submission being approved. Must match `bounty.matched_submission`. Must have status 0 (Pending).
</ParamField>

<ParamField path="null_result" type="Box<Account<NullResult>>" required>
  The NullResult linked to the submission. Used to emit `specimen_number` in event.
</ParamField>

<ParamField path="vault" type="InterfaceAccount<TokenAccount> (mut)" required>
  The bounty's vault token account. Seeds: `["bounty_vault", bounty.key()]`
</ParamField>

<ParamField path="researcher_usdc_ata" type="InterfaceAccount<TokenAccount> (mut)" required>
  Researcher's BIO token associated token account. Receives `reward - fee`. Field name is `researcher_usdc_ata` in code but holds BIO tokens.
</ParamField>

<ParamField path="treasury_usdc_ata" type="InterfaceAccount<TokenAccount> (mut)" required>
  Treasury BIO token associated token account. Receives protocol fee. Field name is `treasury_usdc_ata` in code but holds BIO tokens.
</ParamField>

<ParamField path="protocol_state" type="Box<Account<ProtocolState>>" required>
  Protocol state to read `fee_basis_points`.
</ParamField>

<ParamField path="usdc_mint" type="InterfaceAccount<Mint>" required>
  BIO token mint for decimal validation in transfers. Field name is `usdc_mint` in code but refers to the BIO mint.
</ParamField>

<ParamField path="token_program" type="Interface<TokenInterface>" required>
  SPL Token program for CPI transfers.
</ParamField>

### Validation

* `bounty.status == 1` (line 157) → else returns `NullGraphError::InvalidBountyStatus`
* `bounty.matched_submission == submission.key()` (line 158) → else returns `NullGraphError::SubmissionMismatch`
* `submission.status == 0` (line 164) → else returns `NullGraphError::InvalidSubmissionStatus`
* All arithmetic operations use `checked_mul`, `checked_div`, `checked_sub` → else returns `NullGraphError::FeeOverflow`

### Behavior

1. Validates bounty status, submission match, and submission status
2. Calculates fee: `fee = (reward_amount * fee_basis_points) / 10000`
3. Calculates payout: `payout = reward_amount - fee`
4. Derives vault signer seeds: `["bounty_vault", bounty.key(), &[bounty.vault_bump]]`
5. Transfers `payout` BIO tokens from vault to researcher via CPI with signer
6. If `fee > 0`, transfers `fee` BIO tokens from vault to treasury via CPI with signer
7. Updates `bounty.status` to 2 (Fulfilled)
8. Updates `submission.status` to 1 (Approved)
9. Emits `BountyFulfilled` event with breakdown

### Code Reference

```rust theme={null}
pub fn approve_bounty_submission(ctx: Context<ApproveBountySubmission>) -> Result<()> {
    let bounty = &mut ctx.accounts.bounty;
    require!(bounty.status == 1, NullGraphError::InvalidBountyStatus); // Must be Matched
    require!(
        bounty.matched_submission == ctx.accounts.submission.key(),
        NullGraphError::SubmissionMismatch
    );

    let submission = &mut ctx.accounts.submission;
    require!(submission.status == 0, NullGraphError::InvalidSubmissionStatus); // Must be Pending

    let protocol = &ctx.accounts.protocol_state;
    let fee_bps = protocol.fee_basis_points as u64;
    let total = bounty.reward_amount;
    let fee = total
        .checked_mul(fee_bps)
        .ok_or(NullGraphError::FeeOverflow)?
        .checked_div(10_000)
        .ok_or(NullGraphError::FeeOverflow)?;
    let payout = total.checked_sub(fee).ok_or(NullGraphError::FeeOverflow)?;

    let decimals = ctx.accounts.usdc_mint.decimals;
    let bounty_key = bounty.key();
    let vault_seeds: &[&[u8]] = &[
        b"bounty_vault",
        bounty_key.as_ref(),
        &[bounty.vault_bump],
    ];

    // Pay researcher
    transfer_checked(
        CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            TransferChecked {
                from: ctx.accounts.vault.to_account_info(),
                mint: ctx.accounts.usdc_mint.to_account_info(),
                to: ctx.accounts.researcher_usdc_ata.to_account_info(),
                authority: ctx.accounts.vault.to_account_info(),
            },
            &[vault_seeds],
        ),
        payout,
        decimals,
    )?;

    // Pay treasury fee
    if fee > 0 {
        transfer_checked(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                TransferChecked {
                    from: ctx.accounts.vault.to_account_info(),
                    mint: ctx.accounts.usdc_mint.to_account_info(),
                    to: ctx.accounts.treasury_usdc_ata.to_account_info(),
                    authority: ctx.accounts.vault.to_account_info(),
                },
                &[vault_seeds],
            ),
            fee,
            decimals,
        )?;
    }

    bounty.status = 2; // Fulfilled
    submission.status = 1; // Approved

    emit!(BountyFulfilled {
        bounty_number: bounty.bounty_number,
        specimen_number: ctx.accounts.null_result.specimen_number,
        researcher: submission.researcher,
        payout,
        fee,
    });
    Ok(())
}
```

<Tip>
  With the default fee of 250 basis points (2.5%), a 100 BIO bounty pays 97.5 BIO to the researcher and 2.5 BIO to the treasury.
</Tip>

***

## close\_bounty

Bounty creator reclaims escrowed BIO tokens from an Open or Matched bounty, setting status to Closed.

**Location**: `lib.rs:231-273`

### Parameters

None. All data derived from accounts.

### Accounts

<ParamField path="creator" type="Signer (mut)" required>
  Bounty creator. Must match `bounty.creator`.
</ParamField>

<ParamField path="bounty" type="Account<NullBounty> (mut)" required>
  The bounty being closed. Must satisfy `has_one = creator` constraint. Must have status 0 (Open) or 1 (Matched).
</ParamField>

<ParamField path="vault" type="InterfaceAccount<TokenAccount> (mut)" required>
  The bounty's vault token account. Seeds: `["bounty_vault", bounty.key()]`
</ParamField>

<ParamField path="creator_usdc_ata" type="InterfaceAccount<TokenAccount> (mut)" required>
  Creator's BIO token associated token account. Receives the refund. Field name is `creator_usdc_ata` in code but holds BIO tokens.
</ParamField>

<ParamField path="usdc_mint" type="InterfaceAccount<Mint>" required>
  BIO token mint for decimal validation. Field name is `usdc_mint` in code but refers to the BIO mint.
</ParamField>

<ParamField path="token_program" type="Interface<TokenInterface>" required>
  SPL Token program for CPI transfer.
</ParamField>

### Validation

* `bounty.status == 0 || bounty.status == 1` (line 233) → else returns `NullGraphError::InvalidBountyStatus`

### Behavior

1. Validates bounty status is Open (0) or Matched (1)
2. Reads the vault's current token balance
3. Derives vault signer seeds: `["bounty_vault", bounty.key(), &[bounty.vault_bump]]`
4. If `vault_balance > 0`, transfers entire balance back to creator via CPI with signer
5. Updates `bounty.status` to 3 (Closed)
6. Emits `BountyClosed` event with refunded amount

### Code Reference

```rust theme={null}
pub fn close_bounty(ctx: Context<CloseBounty>) -> Result<()> {
    let bounty = &mut ctx.accounts.bounty;
    require!(
        bounty.status == 0 || bounty.status == 1,
        NullGraphError::InvalidBountyStatus
    );

    let vault_balance = ctx.accounts.vault.amount;
    let decimals = ctx.accounts.usdc_mint.decimals;
    let bounty_key = bounty.key();
    let vault_seeds: &[&[u8]] = &[
        b"bounty_vault",
        bounty_key.as_ref(),
        &[bounty.vault_bump],
    ];

    // Refund creator
    if vault_balance > 0 {
        transfer_checked(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                TransferChecked {
                    from: ctx.accounts.vault.to_account_info(),
                    mint: ctx.accounts.usdc_mint.to_account_info(),
                    to: ctx.accounts.creator_usdc_ata.to_account_info(),
                    authority: ctx.accounts.vault.to_account_info(),
                },
                &[vault_seeds],
            ),
            vault_balance,
            decimals,
        )?;
    }

    bounty.status = 3; // Closed

    emit!(BountyClosed {
        bounty_number: bounty.bounty_number,
        creator: bounty.creator,
        refunded_amount: vault_balance,
    });
    Ok(())
}
```

<Warning>
  Once a bounty is approved (status 2 - Fulfilled), it cannot be closed. Attempting to call `close_bounty` on a fulfilled bounty will fail with `InvalidBountyStatus`.
</Warning>
