Protocol Fees
NullGraph uses a configurable fee mechanism to sustain protocol development, incentivize participation, and build a treasury for future ecosystem growth.
The default fee is 2.5% (250 basis points) deducted from every bounty settlement. Fees are routed to a protocol-controlled treasury wallet.
Fee Model Overview
When Fees Apply
Fees are only charged on successful bounty settlements via the approve_bounty_submission instruction.
NKA Submission No fee — researchers submit null results for free
Bounty Creation No fee — creating bounties is free (only escrow cost)
Bounty Approval 2.5% fee — deducted from reward on settlement
Fee Calculation
Fees are calculated using basis points (1 bp = 0.01%):
let fee_bps = protocol . fee_basis_points as u64 ; // 250 = 2.5%
let total = bounty . reward_amount;
let fee = total
. checked_mul ( fee_bps ) // total * 250
. ok_or ( NullGraphError :: FeeOverflow ) ?
. checked_div ( 10_000 ) // ÷ 10,000 = 2.5%
. ok_or ( NullGraphError :: FeeOverflow ) ? ;
let payout = total . checked_sub ( fee ) . ok_or ( NullGraphError :: FeeOverflow ) ? ;
Example:
Bounty Reward: 100 BIO (100_000_000 base units)
Fee (2.5%): 2.5 BIO (2_500_000 base units)
Researcher Gets: 97.5 BIO (97_500_000 base units)
All arithmetic uses checked operations (checked_mul, checked_div, checked_sub) to prevent overflow and underflow attacks.
Protocol State Configuration
Fees are stored in the global ProtocolState singleton:
pub struct ProtocolState {
pub authority : Pubkey , // Protocol admin wallet
pub nka_counter : u64 , // Auto-incrementing NKA counter
pub bounty_counter : u64 , // Auto-incrementing bounty counter
pub fee_basis_points : u16 , // Fee on settlement (250 = 2.5%)
pub treasury : Pubkey , // Treasury wallet for collected fees
pub bump : u8 , // PDA bump seed
}
Seeds: ["protocol_state"]
Initialization
The protocol is initialized once via initialize_protocol:
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 ; // Set fee rate
state . treasury = ctx . accounts . treasury . key (); // Set treasury wallet
state . bump = ctx . bumps . protocol_state;
emit! ( ProtocolInitialized {
authority : state . authority,
fee_basis_points ,
});
Ok (())
}
One-time setup script:
const initProtocol = async (
program : Program ,
authority : Keypair ,
treasuryAddress : PublicKey
) => {
const [ protocolState ] = PublicKey . findProgramAddressSync (
[ Buffer . from ( 'protocol_state' )],
program . programId
);
const tx = await program . methods
. initializeProtocol (
250 // 2.5% fee
)
. accounts ({
authority: authority . publicKey ,
protocolState ,
treasury: treasuryAddress ,
})
. signers ([ authority ])
. rpc ();
console . log ( 'Protocol initialized with 2.5% fee' );
console . log ( `Treasury: ${ treasuryAddress . toBase58 () } ` );
};
The fee rate is configurable at initialization but cannot be changed afterward in the current implementation. Future versions may add governance-controlled fee updates.
Treasury Distribution
Fee Collection Flow
When a bounty is approved, the vault transfers fees to the treasury in the same transaction:
// Pay researcher (97.5%)
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 , // 97.5%
decimals ,
) ? ;
// Pay treasury fee (2.5%)
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 , // 2.5%
decimals ,
) ? ;
}
Atomic Settlement Both transfers happen in one transaction — researcher payout and treasury fee are inseparable. Either both succeed or both fail.
Treasury Account Derivation
The treasury receives BIO via its Associated Token Account (ATA):
import { getAssociatedTokenAddressSync } from '@solana/spl-token' ;
const treasuryWallet = new PublicKey ( 'TreasuryWalletAddress...' );
const BIO_MINT = new PublicKey ( 'BioTokenMintAddress...' );
const treasuryAta = getAssociatedTokenAddressSync (
BIO_MINT ,
treasuryWallet ,
false , // allowOwnerOffCurve
TOKEN_2022_PROGRAM_ID
);
// This ATA receives all protocol fees
Fee Breakdown Example
Let’s trace a 1000 BIO bounty settlement:
Step Amount (BIO) Base Units Recipient Bounty Reward 1000.00 1_000_000_000 Vault (escrowed) Fee (2.5%) 25.00 25_000_000 Treasury Researcher Payout 975.00 975_000_000 Researcher Vault Balance After 0.00 0 —
On-chain event:
emit! ( BountyFulfilled {
bounty_number : 42 ,
specimen_number : 17 ,
researcher : researcher_pubkey ,
payout : 975_000_000 , // 975 BIO
fee : 25_000_000 , // 25 BIO
});
Fee Basis Points Explained
Basis Points Percentage Calculation 00% 0 / 10_000 = 01001% 100 / 10_000 = 0.012502.5% 250 / 10_000 = 0.0255005% 500 / 10_000 = 0.05100010% 1_000 / 10_000 = 0.10
Basis points provide precision for fractional percentages (like 2.5%) using only integer arithmetic — critical for on-chain programs.
Why 2.5%?
NullGraph’s fee model balances sustainability with accessibility:
Lower than DeFi Most DeFi protocols charge 3-5% fees. NullGraph’s 2.5% is competitive for a scientific data marketplace.
No Creator Fee Unlike NFT marketplaces, bounty creators pay zero fees — only settlement is taxed, incentivizing bounty creation.
Researcher-Friendly 97.5% payout ensures researchers capture most of the value they create.
Sustainable Treasury Fees accumulate in BIO, building a war chest for grants, development, and ecosystem growth.
Treasury Use Cases
Fees collected in the treasury can fund:
Protocol Development — smart contract upgrades, frontend improvements
Community Grants — funding for researchers and BioDAOs
Verification Incentives — rewards for NKA peer review and validation
Marketing & Growth — ecosystem expansion, conference sponsorships
Liquidity Mining — future token incentives for participation
Treasury governance mechanisms are not yet implemented . The current treasury wallet is controlled by the protocol authority. Future versions may introduce DAO governance.
Fee Events & Tracking
Every settlement emits a BountyFulfilled event with exact fee amounts:
#[event]
pub struct BountyFulfilled {
pub bounty_number : u64 ,
pub specimen_number : u64 ,
pub researcher : Pubkey ,
pub payout : u64 , // Amount sent to researcher
pub fee : u64 , // Amount sent to treasury
}
Indexing example:
const connection = new Connection ( RPC_URL );
const program = new Program ( IDL , PROGRAM_ID , { connection });
program . addEventListener ( 'BountyFulfilled' , ( event ) => {
const payout = event . payout . toNumber () / 1_000_000 ; // BIO
const fee = event . fee . toNumber () / 1_000_000 ; // BIO
const total = payout + fee ;
console . log ( `Bounty NB- ${ event . bountyNumber } settled:` );
console . log ( ` Total: ${ total } BIO` );
console . log ( ` Researcher: ${ payout } BIO ( ${ ( payout / total * 100 ). toFixed ( 2 ) } %)` );
console . log ( ` Treasury: ${ fee } BIO ( ${ ( fee / total * 100 ). toFixed ( 2 ) } %)` );
});
Security: Safe Math
All fee calculations use checked arithmetic to prevent exploits:
// SAFE: Uses checked_mul and checked_div
let fee = total
. checked_mul ( fee_bps )
. ok_or ( NullGraphError :: FeeOverflow ) ?
. checked_div ( 10_000 )
. ok_or ( NullGraphError :: FeeOverflow ) ? ;
// SAFE: Uses checked_sub
let payout = total
. checked_sub ( fee )
. ok_or ( NullGraphError :: FeeOverflow ) ? ;
Why this matters:
Overflow Protection Prevents attackers from causing integer overflow with massive reward amounts
Underflow Protection Ensures payout = total - fee never underflows (e.g., if fee > total)
Deterministic Failure Returns clear FeeOverflow error instead of silent corruption
Auditable Explicit error handling makes fee logic easy to audit
Frontend Fee Display
Show fee breakdown before settlement:
const FeeSummary = ({ rewardAmount } : { rewardAmount : number }) => {
const FEE_BPS = 250 ; // 2.5%
const fee = ( rewardAmount * FEE_BPS ) / 10_000 ;
const payout = rewardAmount - fee ;
return (
< div className = "fee-summary" >
< div className = "line" >
< span > Bounty Reward : </ span >
< span >{rewardAmount.toFixed( 2 ) } BIO </ span >
</ div >
< div className = "line fee" >
< span > Protocol Fee ( 2.5 % ) : </ span >
< span > - {fee.toFixed( 2 ) } BIO </ span >
</ div >
< div className = "line total" >
< span > You Receive : </ span >
< span >{payout.toFixed( 2 ) } BIO </ span >
</ div >
</ div >
);
};
Querying Treasury Balance
Check total fees collected:
const getTreasuryBalance = async (
connection : Connection ,
treasuryWallet : PublicKey ,
bioMint : PublicKey
) => {
const treasuryAta = getAssociatedTokenAddressSync (
bioMint ,
treasuryWallet ,
false ,
TOKEN_2022_PROGRAM_ID
);
const account = await connection . getTokenAccountBalance ( treasuryAta );
const balance = parseFloat ( account . value . uiAmount || '0' );
console . log ( `Treasury Balance: ${ balance } BIO` );
return balance ;
};
No Fees on Closed Bounties
When a bounty is closed (not approved), the full vault balance returns to the creator:
pub fn close_bounty ( ctx : Context < CloseBounty >) -> Result <()> {
// ...
let vault_balance = ctx . accounts . vault . amount;
// Refund creator (100% — no fee)
if vault_balance > 0 {
transfer_checked (
// ... transfer full vault_balance to creator ...
) ? ;
}
emit! ( BountyClosed {
refunded_amount : vault_balance , // Full amount
});
Ok (())
}
Closing a bounty incurs zero fees — creators get 100% of their escrowed BIO back. Fees only apply on successful settlements.
Fee Rate Immutability
The current implementation sets the fee rate once at initialization :
✅ Transparent — fee rate is public, stored on-chain
✅ Predictable — users know exact fees before transacting
❌ Not governable — cannot be changed without program upgrade
Future governance:
A future version may add:
pub fn update_fee_rate (
ctx : Context < UpdateFeeRate >,
new_fee_bps : u16 ,
) -> Result <()> {
// Require DAO governance vote or multisig authority
let state = & mut ctx . accounts . protocol_state;
state . fee_basis_points = new_fee_bps ;
emit! ( FeeRateUpdated { new_fee_bps });
Ok (())
}
Comparison with Other Protocols
Protocol Fee Model Fee Rate NullGraph Settlement fee 2.5% Uniswap V3 Swap fee 0.05-1% (per swap) OpenSea Marketplace fee 2.5% Magic Eden Marketplace fee 2% Tensor Marketplace fee 1.5% (dynamic) Aave Borrow fee Variable (0-8%)
NullGraph’s 2.5% is competitive with Web3 marketplace standards and lower than most DeFi lending protocols.
Next Steps
BIO Integration Learn how BIO tokens power the bounty economy
Bounty Marketplace Explore the full bounty lifecycle and escrow mechanics