Skip to main content
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

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%)

Accounts

Signer (mut)
required
Protocol admin wallet. Becomes the authority stored in ProtocolState. Pays for account creation.
Account<ProtocolState> (init)
required
The ProtocolState singleton PDA being initialized. Seeds: ["protocol_state"]
UncheckedAccount
required
Treasury wallet address for fee collection. Not validated on-chain; admin must ensure it has a BIO token account.
Program<System>
required
Solana System Program for account creation.

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

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.

submit_null_result

Submits a new Null Knowledge Asset (NKA) to the protocol. Any wallet can call this. Location: lib.rs:37-70

Parameters

[u8; 128]
required
UTF-8 encoded hypothesis tested. Maximum 128 bytes. Pad with zeros if shorter.
[u8; 128]
required
UTF-8 encoded methodology summary. Maximum 128 bytes. Pad with zeros if shorter.
[u8; 128]
required
UTF-8 encoded expected outcome description. Maximum 128 bytes. Pad with zeros if shorter.
[u8; 128]
required
UTF-8 encoded actual outcome description. Maximum 128 bytes. Pad with zeros if shorter.
u32
required
Statistical p-value as fixed-point integer (multiply by 10000). Example: 4200 = 0.42
u32
required
Number of subjects or observations in the experiment.
[u8; 32]
required
SHA-256 hash of the underlying dataset. Can be all zeros if no file attached.

Accounts

Signer (mut)
required
The researcher submitting the NKA. Pays for NullResult PDA creation.
Account<ProtocolState> (mut)
required
The protocol state singleton. nka_counter will be incremented.
Account<NullResult> (init)
required
The NullResult PDA being created. Seeds: ["null_result", researcher.key(), &(nka_counter + 1).to_le_bytes()]
Program<System>
required
Solana System Program for account creation.

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


create_bounty

Creates a new bounty and escrows BIO tokens into a PDA-controlled vault. Location: lib.rs:72-121

Parameters

[u8; 256]
required
UTF-8 encoded description of the desired null result. Maximum 256 bytes. Pad with zeros if shorter.
u64
required
BIO token reward in base units (6 decimals). Must be > 0 or instruction fails with InvalidRewardAmount.Example: 1000000 = 1 BIO
i64
required
Unix timestamp deadline for submissions. Currently not enforced by program logic.

Accounts

Signer (mut)
required
Bounty creator wallet. Pays for PDA creation and must approve BIO token transfer.
Account<ProtocolState> (mut)
required
Protocol state singleton. bounty_counter will be incremented.
Account<NullBounty> (init)
required
The NullBounty PDA being created. Seeds: ["null_bounty", creator.key(), &(bounty_counter + 1).to_le_bytes()]
InterfaceAccount<TokenAccount> (init)
required
The vault token account PDA for escrowed BIO tokens. Seeds: ["bounty_vault", bounty.key()]. Authority is the vault itself.
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.
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).
Interface<TokenInterface>
required
SPL Token program for CPI transfer.
Program<AssociatedToken>
required
Associated Token program for vault creation.
Program<System>
required
System program for account creation.

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


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

Signer (mut)
required
The researcher submitting their NKA. Must be the owner of the null_result account. Pays for BountySubmission PDA creation.
Account<NullResult>
required
The NullResult PDA being submitted. Must satisfy has_one = researcher constraint.
Account<NullBounty> (mut)
required
The target bounty. Must have status 0 (Open).
Account<BountySubmission> (init)
required
The BountySubmission PDA being created. Seeds: ["bounty_submission", bounty.key(), null_result.key()]
Program<System>
required
System program for account creation.

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

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

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

Signer (mut)
required
Bounty creator. Must match bounty.creator.
Box<Account<NullBounty>> (mut)
required
The bounty being fulfilled. Must satisfy has_one = creator constraint. Must have status 1 (Matched).
Box<Account<BountySubmission>> (mut)
required
The submission being approved. Must match bounty.matched_submission. Must have status 0 (Pending).
Box<Account<NullResult>>
required
The NullResult linked to the submission. Used to emit specimen_number in event.
InterfaceAccount<TokenAccount> (mut)
required
The bounty’s vault token account. Seeds: ["bounty_vault", bounty.key()]
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.
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.
Box<Account<ProtocolState>>
required
Protocol state to read fee_basis_points.
InterfaceAccount<Mint>
required
BIO token mint for decimal validation in transfers. Field name is usdc_mint in code but refers to the BIO mint.
Interface<TokenInterface>
required
SPL Token program for CPI transfers.

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

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.

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

Signer (mut)
required
Bounty creator. Must match bounty.creator.
Account<NullBounty> (mut)
required
The bounty being closed. Must satisfy has_one = creator constraint. Must have status 0 (Open) or 1 (Matched).
InterfaceAccount<TokenAccount> (mut)
required
The bounty’s vault token account. Seeds: ["bounty_vault", bounty.key()]
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.
InterfaceAccount<Mint>
required
BIO token mint for decimal validation. Field name is usdc_mint in code but refers to the BIO mint.
Interface<TokenInterface>
required
SPL Token program for CPI transfer.

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

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.