Skip to content

Latest commit

Β 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Solfact: SOL-Based Escrow Staking Poll Voting System

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.

πŸ“‹ Table of Contents

🎯 Overview

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.

Key Features

  • 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

How It Works

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

πŸ—οΈ Architecture

System Components

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   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     β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data Structures

Poll Account

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
}

VoteAccount

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
}

Escrow Account

pub struct Escrow {
    pub poll: Pubkey,           // Associated poll
    pub bump: u8,               // PDA bump seed
}

πŸ“ Project Structure

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

πŸš€ Quick Start

Prerequisites

  • Node.js v18+ and npm
  • Rust and Anchor CLI (for program development)
  • Solana CLI (for deployment)
  • Git

Setup in 3 Steps

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

πŸ“¦ Installation

SDK Installation

cd solfact-sdk
npm install

Program Setup

cd solfact-program

# Install dependencies
cargo fetch

# Build the program
anchor build

# Run tests (requires local Solana validator)
anchor test

πŸ’» Usage

Using the SDK

Basic Setup

import * 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 Poll

// 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 on a Poll

// 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
);

Resolve and Claim

// 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
);

Query Poll State

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

Using the Program Directly

Create Poll Instruction

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();

πŸ“š API Reference

SolfactSDK Class

Methods

createFactCheck()

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 signing
  • news_hash: Unique identifier for the news
  • deadlineTs: Unix timestamp for voting deadline
  • creatorChoice: Creator's vote (true=FACT, false=HOAX)
  • createStakeLamports: Amount to stake in lamports

Returns: Poll PDA public key


voteFact()

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


voteHoax()

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


resolveFactCheck()

Resolve the poll after deadline - determines winner.

async resolveFactCheck(
  callerKeypair: Keypair,
  pollPda: PublicKey,
  news_hash: string
): Promise<void>

claimReward()

Claim reward from escrow (only for winners).

async claimReward(
  claimerKeypair: Keypair,
  pollPda: PublicKey,
  votePda: PublicKey,
  news_hash: string
): Promise<void>

getFactCheckState()

Fetch the current state of a poll.

async getFactCheckState(pollPda: PublicKey): Promise<FactCheckState>

Returns: Poll state object


pdasFromHash()

Derive PDAs for a news hash without on-chain queries.

async pdasFromHash(news_hash: string): Promise<{
  poll: PublicKey;
  escrow: PublicKey;
}>

Utility Functions

derivePollPda()

async derivePollPda(
  programId: PublicKey,
  news_hash: string
): Promise<[PublicKey, number]>

deriveEscrowPda()

async deriveEscrowPda(
  programId: PublicKey,
  news_hash: string
): Promise<[PublicKey, number]>

deriveVotePda()

async deriveVotePda(
  programId: PublicKey,
  poll: PublicKey,
  voter: PublicKey
): Promise<[PublicKey, number]>

systemTransferInstruction()

function systemTransferInstruction(
  from: PublicKey,
  to: PublicKey,
  lamports: number
): TransactionInstruction

πŸ› οΈ Development

Building the Program

cd solfact-program
anchor build

This generates:

  • target/deploy/solfact.so - Compiled program
  • target/idl/solfact.json - Program IDL

Building the SDK

cd solfact-sdk
npm run build

Output in dist/ directory

Code Organization

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 class
  • types.ts: TypeScript interfaces
  • utils.ts: Helper functions
  • test.ts: Example usage

πŸ§ͺ Testing

Run SDK Tests

cd solfact-sdk
npm run build  # Build first
npm test       # Run tests

Run Program Tests

cd solfact-program

# Start local Solana validator
solana-test-validator

# In another terminal
anchor test

Test Coverage

The 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

🚒 Deployment

Local Testing

# 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

Devnet Deployment

# Configure for devnet
solana config set --url https://api.devnet.solana.com

# Deploy
cd solfact-program
anchor deploy --provider.cluster devnet

Mainnet Deployment

# ⚠️ Requires audited code and careful configuration
solana config set --url https://api.mainnet-beta.solana.com
anchor deploy --provider.cluster mainnet-beta

Post-Deployment

After deployment, update all references to the program ID:

  1. Anchor.toml

    [programs.mainnet-beta]
    solfact = "YOUR_DEPLOYED_PROGRAM_ID"
  2. lib.rs

    declare_id!("YOUR_DEPLOYED_PROGRAM_ID");
  3. solfact.json

    {
      "metadata": {
        "address": "YOUR_DEPLOYED_PROGRAM_ID"
      }
    }

πŸ”’ Security Considerations

Current Implementation

  • βœ… Anchor built-in account validation
  • βœ… PDA-based account derivation
  • βœ… Checked arithmetic (overflow protection)
  • βœ… Deadline enforcement
  • βœ… Winner validation before payout

Recommendations for Production

  1. Audit: Get smart contract audited by professional auditors
  2. Bump Seeds: Verify bump seed calculations
  3. Reentrancy: Add guards against reentrancy attacks
  4. Limits: Implement caps on poll count per creator
  5. Rate Limiting: Throttle claim rewards instructions
  6. Governance: Add multi-sig authority for upgrades

πŸ“Š Constants & Limits

// 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

πŸ› Troubleshooting

"Resource not accessible by integration" on GitHub deployment

  • Solution: Create repos manually or use personal GitHub token with repo scope

"Program not found" when claiming rewards

  • Ensure program is deployed to network specified in connection URL
  • Verify program ID in IDL matches deployed program

"Insufficient funds" errors

  • Request SOL airdrop on devnet: solana airdrop 2
  • Use faucet or fund account for mainnet

"Deadline not met" when voting

  • Check server time vs poll deadline
  • Ensure unix timestamps in seconds (not milliseconds)

πŸ“„ License

MIT License - See LICENSE file

🀝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Test thoroughly
  5. Submit pull request

πŸ“ž Support

For issues and questions:


Built with ❀️ for the Solana ecosystem

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages