A decentralized fact-checking protocol on Solana where users stake SOL to vote on news credibility (FACT vs HOAX). Built with Anchor and TypeScript, featuring an escrow-based reward distribution mechanism.
- Overview
- Architecture
- Project Structure
- Quick Start
- Installation
- Usage
- API Reference
- Development
- Testing
- Deployment
Solfact is a decentralized fact-checking system that leverages blockchain technology and economic incentives to establish truth. Users stake SOL tokens to vote whether news is FACT or HOAX. Winners receive rewards from the escrow pool proportional to their stake.
- SOL-Based Staking: Economic skin-in-the-game mechanism
- Escrow Pool: Losers' stakes fund winner rewards
- Proof-of-Stake Voting: Vote weight proportional to stake amount
- On-Chain Resolution: Transparent, verifiable outcomes
- Reward Distribution: Automatic proportional payout to winners
1. Creator creates a poll with a deadline and stakes SOL (votes FACT or HOAX)
2. Voters stake SOL to vote on the poll outcome
3. After deadline, poll is resolved - majority wins
4. Winners claim rewards from escrow (losers' stakes)
5. Payout = (escrow_balance Γ voter_stake) / total_winning_stake
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Solana Network β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Solfact Anchor Program β β
β β (Program ID: SoLFACt1111...) β β
β β β β
β β Instructions: β β
β β β’ create_poll β Initialize poll β β
β β β’ vote β Cast vote + stake β β
β β β’ resolve_poll β Determine winner β β
β β β’ claim_reward β Distribute rewards β β
β β β β
β β Accounts: β β
β β β’ Poll PDA β Poll state β β
β β β’ Escrow PDA β Reward pool β β
β β β’ VoteAccount PDA β Vote records β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
ββββββββββββββββββββ¬βββββββββββββββ
β
TypeScript/Node.js
ββββββββββββββββββββ
β SolfactSDK β
β Client Library β
ββββββββββββββββββββ
β
ββββββββββββββββββββ
β Frontend Apps β
β Scripts β
β Bot Services β
ββββββββββββββββββββ
pub struct Poll {
pub news_hash: String, // Unique identifier for the news
pub creator: Pubkey, // Poll creator address
pub deadline: i64, // Unix timestamp deadline
pub total_yes: u64, // Total lamports staked on FACT
pub total_no: u64, // Total lamports staked on HOAX
pub resolved: bool, // Whether poll is resolved
pub winner: u8, // 0=none, 1=FACT, 2=HOAX
pub bump: u8, // PDA bump seed
}pub struct VoteAccount {
pub voter: Pubkey, // Voter address
pub poll: Pubkey, // Associated poll
pub choice: u8, // 1=FACT, 2=HOAX
pub stake: u64, // Lamports staked
pub claimed: bool, // Reward claimed status
pub bump: u8, // PDA bump seed
}pub struct Escrow {
pub poll: Pubkey, // Associated poll
pub bump: u8, // PDA bump seed
}solfact/
βββ solfact-program/ # Anchor smart contract
β βββ Anchor.toml # Anchor configuration
β βββ Cargo.toml # Workspace manifest
β βββ programs/solfact/ # Program crate
β β βββ Cargo.toml # Program dependencies
β β βββ src/
β β βββ lib.rs # Program implementation
β βββ tests/
β βββ solfact.ts # Integration tests
β
βββ solfact-sdk/ # TypeScript client SDK
βββ package.json # Dependencies & scripts
βββ tsconfig.json # TypeScript config
βββ src/
β βββ SolfactSDK.ts # Main SDK class
β βββ types.ts # Type definitions
β βββ utils.ts # PDA derivation & helpers
β βββ test.ts # Example usage
β βββ idl/
β βββ solfact.json # Program IDL
βββ dist/ # Compiled output
- Node.js v18+ and npm
- Rust and Anchor CLI (for program development)
- Solana CLI (for deployment)
- Git
# 1. Clone the repository
git clone https://github.com/0xAlchemistis/solfact.git
cd solfact
# 2. Setup SDK
cd solfact-sdk
npm install
npm run build
# 3. Deploy program (requires Solana setup)
cd ../solfact-program
anchor build
anchor deploycd solfact-sdk
npm installcd solfact-program
# Install dependencies
cargo fetch
# Build the program
anchor build
# Run tests (requires local Solana validator)
anchor testimport * as anchor from "@coral-xyz/anchor";
import { Keypair, Connection, LAMPORTS_PER_SOL } from "@solana/web3.js";
import { SolfactSDK } from "./SolfactSDK";
// Initialize connection
const connection = new Connection("http://127.0.0.1:8899", "confirmed");
const wallet = new anchor.Wallet(Keypair.generate());
// Initialize SDK
const sdk = new SolfactSDK(connection, wallet);// Create a fact-check poll
const newsHash = "article-uuid-123";
const deadline = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
const creatorChoice = true; // Creator votes FACT
const stakeAmount = 1 * LAMPORTS_PER_SOL; // 1 SOL
const pollPda = await sdk.createFactCheck(
creatorKeypair,
newsHash,
deadline,
creatorChoice,
stakeAmount
);
console.log("Poll created:", pollPda.toBase58());// Vote FACT
const votePda = await sdk.voteFact(
voterKeypair,
pollPda,
newsHash,
0.5 * LAMPORTS_PER_SOL
);
// Vote HOAX
const votePda = await sdk.voteHoax(
voterKeypair,
pollPda,
newsHash,
0.5 * LAMPORTS_PER_SOL
);// Wait for deadline to pass, then resolve
await sdk.resolveFactCheck(
resolverKeypair,
pollPda,
newsHash
);
// Claim reward (if on winning side)
await sdk.claimReward(
voterKeypair,
pollPda,
votePda,
newsHash
);const pollState = await sdk.getFactCheckState(pollPda);
console.log("Poll winner:", pollState.winner); // 1=FACT, 2=HOAX
console.log("Total FACT votes:", pollState.total_yes);
console.log("Total HOAX votes:", pollState.total_no);await program.methods
.createPoll(newsHash, new anchor.BN(deadline), true)
.accounts({
creator: creatorKeypair.publicKey,
poll: pollPda,
escrow: escrowPda,
creatorStake: creatorKeypair.publicKey,
systemProgram: SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
})
.signers([creatorKeypair])
.preInstructions([transferIx])
.rpc();Creates a new fact-check poll with creator's stake.
async createFactCheck(
creatorKeypair: Keypair,
news_hash: string,
deadlineTs: number,
creatorChoice: boolean,
createStakeLamports: number
): Promise<PublicKey>Parameters:
creatorKeypair: Creator's keypair for signingnews_hash: Unique identifier for the newsdeadlineTs: Unix timestamp for voting deadlinecreatorChoice: Creator's vote (true=FACT, false=HOAX)createStakeLamports: Amount to stake in lamports
Returns: Poll PDA public key
Vote that news is FACT with staked SOL.
async voteFact(
voterKeypair: Keypair,
pollPda: PublicKey,
news_hash: string,
stakeLamports: number
): Promise<PublicKey>Returns: Vote account PDA
Vote that news is HOAX with staked SOL.
async voteHoax(
voterKeypair: Keypair,
pollPda: PublicKey,
news_hash: string,
stakeLamports: number
): Promise<PublicKey>Returns: Vote account PDA
Resolve the poll after deadline - determines winner.
async resolveFactCheck(
callerKeypair: Keypair,
pollPda: PublicKey,
news_hash: string
): Promise<void>Claim reward from escrow (only for winners).
async claimReward(
claimerKeypair: Keypair,
pollPda: PublicKey,
votePda: PublicKey,
news_hash: string
): Promise<void>Fetch the current state of a poll.
async getFactCheckState(pollPda: PublicKey): Promise<FactCheckState>Returns: Poll state object
Derive PDAs for a news hash without on-chain queries.
async pdasFromHash(news_hash: string): Promise<{
poll: PublicKey;
escrow: PublicKey;
}>async derivePollPda(
programId: PublicKey,
news_hash: string
): Promise<[PublicKey, number]>async deriveEscrowPda(
programId: PublicKey,
news_hash: string
): Promise<[PublicKey, number]>async deriveVotePda(
programId: PublicKey,
poll: PublicKey,
voter: PublicKey
): Promise<[PublicKey, number]>function systemTransferInstruction(
from: PublicKey,
to: PublicKey,
lamports: number
): TransactionInstructioncd solfact-program
anchor buildThis generates:
target/deploy/solfact.so- Compiled programtarget/idl/solfact.json- Program IDL
cd solfact-sdk
npm run buildOutput in dist/ directory
Program (solfact-program/programs/solfact/src/lib.rs):
#[program]module: Instruction handlers#[derive(Accounts)]structs: Account validation- Data structures: Poll, Escrow, VoteAccount
- Error types: Custom error codes
SDK (solfact-sdk/src/):
SolfactSDK.ts: Main client classtypes.ts: TypeScript interfacesutils.ts: Helper functionstest.ts: Example usage
cd solfact-sdk
npm run build # Build first
npm test # Run testscd solfact-program
# Start local Solana validator
solana-test-validator
# In another terminal
anchor testThe integration test (tests/solfact.ts) covers:
- β Poll creation with creator stake
- β Voting from multiple accounts
- β Poll resolution after deadline
- β Reward claiming for winners
- β Stake accumulation and distribution
# Terminal 1: Start validator
solana-test-validator
# Terminal 2: Build and deploy
cd solfact-program
anchor deploy
# Get deployed program ID from output
# Update: Anchor.toml, declare_id! in lib.rs, and src/idl/solfact.json# Configure for devnet
solana config set --url https://api.devnet.solana.com
# Deploy
cd solfact-program
anchor deploy --provider.cluster devnet# β οΈ Requires audited code and careful configuration
solana config set --url https://api.mainnet-beta.solana.com
anchor deploy --provider.cluster mainnet-betaAfter deployment, update all references to the program ID:
-
Anchor.toml
[programs.mainnet-beta] solfact = "YOUR_DEPLOYED_PROGRAM_ID"
-
lib.rs
declare_id!("YOUR_DEPLOYED_PROGRAM_ID");
-
solfact.json
{ "metadata": { "address": "YOUR_DEPLOYED_PROGRAM_ID" } }
- β Anchor built-in account validation
- β PDA-based account derivation
- β Checked arithmetic (overflow protection)
- β Deadline enforcement
- β Winner validation before payout
- Audit: Get smart contract audited by professional auditors
- Bump Seeds: Verify bump seed calculations
- Reentrancy: Add guards against reentrancy attacks
- Limits: Implement caps on poll count per creator
- Rate Limiting: Throttle claim rewards instructions
- Governance: Add multi-sig authority for upgrades
// Program space allocations
Poll::MAX_SIZE: 140 bytes
Escrow::MAX_SIZE: 33 bytes
VoteAccount::MAX_SIZE: 74 bytes
// String limits
news_hash: String // 4 + len bytes
// Numerical limits
u64 max stake: 18,446,744,073,709,551,615 lamports
i64 deadline: UTC timestamps- Solution: Create repos manually or use personal GitHub token with
reposcope
- Ensure program is deployed to network specified in connection URL
- Verify program ID in IDL matches deployed program
- Request SOL airdrop on devnet:
solana airdrop 2 - Use faucet or fund account for mainnet
- Check server time vs poll deadline
- Ensure unix timestamps in seconds (not milliseconds)
MIT License - See LICENSE file
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit pull request
For issues and questions:
- GitHub Issues: Create an issue
- Discussions: Start a discussion
Built with β€οΈ for the Solana ecosystem