Sol.College

Full lesson

poe-solana

What it does

From the project README:

We are on a mission to build a fair and scalable prediction market that empowers better decision-making. By addressing the inherent flaws in traditional markets such as front-running and liquidity constraints we are paving the way for futarchy experiments and innovative governance models that could reshape organizations and societies alike.
Original
ProofOfEstimate/poe-solana
SuperStack fork
Blockchains/poe-solana @ 29a7c77
Stars
1
Last push (original)
2026-07-07
Languages
TypeScript, Rust, CSS, JavaScript
Detected stack
AnchorNext.jsweb3.js
Categories
Other
Licence
MIT
Homepage
https://poe-solana-beta.vercel.app

Architecture

117 source files. By extension: .tsx ×64, .ts ×29, .rs ×22, .js ×2.

Top-level directorySource files
app90
programs22
scripts3
migrations1
tests1

On-chain programs: programs/poe

✅ Operational, verified 2026-11-02 Tested on 2026-11-02: full log results.json

Build it yourself: step-by-step guide

Every command below was run on 2026-11-02 by tests/run_fork.py against the pinned commit, and the results come from that run. The full output is in the committed log.

1. Prerequisites (versions we tested with)

cargo install --git https://github.com/coral-xyz/anchor avm --locked
avm install 0.30.1 && avm use 0.30.1
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"

2. Clone at the tested commit

git clone https://github.com/Blockchains/poe-solana.git && cd poe-solana
git checkout 29a7c77667bc68d4bdf2b1cc7a98056af7f8437d

3. Configure

No environment file is needed.

4. Install dependencies

DirectoryCommandResultTime
.npx -y yarn@1 install --ignore-engines --network-timeout 600000✅ pass4s
appnpx -y yarn@1 install --ignore-engines --network-timeout 600000✅ pass12s
.npx -y yarn@1 install --ignore-engines --network-timeout 600000✅ pass1s

5. Build

DirectoryCommandResultTime
.RUSTUP_TOOLCHAIN=nightly-2025-04-01 anchor@0.30.1 build✅ pass143s
appnpx -y yarn@1 run build✅ pass47s

6. Test

DirectoryCommandResultTime
.RUSTUP_TOOLCHAIN=nightly-2025-04-01 anchor@0.30.1 test --skip-build --provider.cluster localnet --provider.wallet ~/.config/solana/id.json✅ pass12s

7. Deploy to a local validator

solana-test-validator --reset   # separate terminal
solana config set -ul && solana airdrop 100
anchor deploy --provider.cluster localnet
DirectoryCommandResultTime
.solana airdrop 100 -u localhost >/dev/null; RUSTUP_TOOLCHAIN=nightly-2025-04-01 anchor@0.30.1 deploy --provider.cluster localnet --provider.wallet ~/.config/solana/id.json✅ pass8s

Proof: program IDs and transactions

Transaction signatures recorded on the local validator (1 shown). Local ledgers are thrown away after the run, so these work as receipts in the log and can't be looked up on an explorer:

Deploy to devnet (free)

solana config set --url devnet
solana airdrop 2   # or https://faucet.solana.com
anchor deploy --provider.cluster devnet   # native: solana program deploy target/deploy/<name>.so
# point the frontend RPC env var at https://api.devnet.solana.com

How it works

Every instruction handler in the program, with the accounts struct it validates. Each link points to the line at the pinned commit.

InstructionAccountsWhere
initializeInitializeprograms/poe/src/lib.rs:17
register_userRegisterUserprograms/poe/src/lib.rs:21
remove_estimateRemoveEstimateprograms/poe/src/lib.rs:70
resolve_pollResolvePollprograms/poe/src/lib.rs:74
collect_pointsCollectPointsprograms/poe/src/lib.rs:78

Extend it

  1. Add an integration test that runs the main flow end to end and asserts every account state change.
  2. Add a negative test: call each instruction with the wrong signer or account and check that it fails.
  3. Add an event (emit!) to each state-changing instruction and subscribe to it from the client.
  4. Replace any unchecked AccountInfo with typed accounts plus has_one/seeds constraints.
  5. Make the frontend read the cluster from an env var, deploy it to GitHub Pages and point it at your devnet program.
  6. Wire the build into CI by copying this site's verify-forks workflow.

Dapp: deploying

This fork is queued for its own live dapp on Sol.College, with a Devnet/Mainnet switch. It appears here, at /dapps/poe-solana/, once it is deployed and checked on devnet. Until then, run it locally with the commands below.

Key code, explained

Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.

ProofOfEstimate/poe-solana · programs/poe/src/contexts/register_user.rs L1–24 @ 29a7c77 · MIT · original
use anchor_lang::prelude::*;use anchor_spl::{    associated_token::AssociatedToken,    token::{mint_to, Mint, MintTo, Token, TokenAccount},}; use crate::states::*; #[derive(Accounts)]pub struct RegisterUser<'info> {    #[account(mut)]    pub payer: Signer<'info>,    #[account(        init,        payer = payer,        seeds=[User::SEED_PREFIX.as_bytes(), payer.key().as_ref()],        space=User::LEN,        bump    )]    pub user: Box<Account<'info, User>>,    #[account(        seeds = ["poeken_mint".as_bytes()],        bump,        mut
  1. Line 4 token::{mint_to, Mint, MintTo, Token, TokenAccount},: Mints new tokens. The mint authority must sign.
  2. Line 12 pub payer: Signer<'info>,: Requires this account to have signed the transaction.
  3. Line 14 init,: Creates and pays for a new account. Space and payer must be specified.
  4. Line 16 seeds=[User::SEED_PREFIX.as_bytes(), payer.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  5. Line 18 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
ProofOfEstimate/poe-solana · programs/poe/src/contexts/make_estimate.rs L13–36 @ 29a7c77 · MIT · original
pub struct MakeEstimate<'info> {    #[account(mut)]    pub forecaster: Signer<'info>,    #[account(        mut,        seeds=[User::SEED_PREFIX.as_bytes(), forecaster.key().as_ref()],        bump=user.bump,      )]    pub user: Box<Account<'info, User>>,    #[account(      mut,      seeds=[Poll::SEED_PREFIX.as_bytes(), &poll.id.to_le_bytes()],      bump=poll.bump    )]    pub poll: Box<Account<'info, Poll>>,    #[account(      init,      payer = forecaster,      seeds=[UserEstimate::SEED_PREFIX.as_bytes(), poll.key().as_ref(), forecaster.key().as_ref()],      space = UserEstimate::LEN,      bump,    )]    pub user_estimate: Box<Account<'info, UserEstimate>>,    // TODO: Instead of init_if_needed, close user_estimate_update accounts when user removes estimate or figure out where to start to count
  1. Line 15 pub forecaster: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 18 seeds=[User::SEED_PREFIX.as_bytes(), forecaster.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 19 bump=user.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 29 init,: Creates and pays for a new account. Space and payer must be specified.
  5. Line 36 // TODO: Instead of init_if_needed, close user_estimate_update accounts when user removes : Creates the account only if it doesn't exist. Watch for re-initialisation bugs.
ProofOfEstimate/poe-solana · programs/poe/src/contexts/update_estimate.rs L13–36 @ 29a7c77 · MIT · original
    pub forecaster: Signer<'info>,    #[account(      mut,      seeds=[Poll::SEED_PREFIX.as_bytes(), &poll.id.to_le_bytes()],      bump=poll.bump    )]    pub poll: Box<Account<'info, Poll>>,    #[account(      mut,      seeds=[UserEstimate::SEED_PREFIX.as_bytes(), poll.key().as_ref(), forecaster.key().as_ref()],      bump = user_estimate.bump,    )]    pub user_estimate: Box<Account<'info, UserEstimate>>,    // TODO: Instead of init_if_needed, close user_estimate_update accounts when user removes estimate, but how to ensure that?    #[account(        init_if_needed,        payer = forecaster,        seeds=[UserEstimateUpdate::SEED_PREFIX.as_bytes(), poll.key().as_ref(), forecaster.key().as_ref(), &user_estimate.num_estimate_updates.to_le_bytes()],        space= UserEstimateUpdate::LEN,        bump,    )]    pub user_estimate_update: Box<Account<'info, UserEstimateUpdate>>,    #[account(        init,
  1. Line 13 pub forecaster: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 16 seeds=[Poll::SEED_PREFIX.as_bytes(), &poll.id.to_le_bytes()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 17 bump=poll.bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 26 // TODO: Instead of init_if_needed, close user_estimate_update accounts when user removes : Creates the account only if it doesn't exist. Watch for re-initialisation bugs.
  5. Line 36 init,: Creates and pays for a new account. Space and payer must be specified.
ProofOfEstimate/poe-solana · programs/poe/src/contexts/remove_estimate.rs L13–36 @ 29a7c77 · MIT · original
        bump=user.bump,      )]    pub user: Box<Account<'info, User>>,    #[account(      mut,      seeds=[Poll::SEED_PREFIX.as_bytes(), &poll.id.to_le_bytes()],      bump=poll.bump    )]    pub poll: Box<Account<'info, Poll>>,    #[account(      mut,      seeds=[UserEstimate::SEED_PREFIX.as_bytes(), poll.key().as_ref(), forecaster.key().as_ref()],      bump = user_estimate.bump,      close = forecaster    )]    pub user_estimate: Box<Account<'info, UserEstimate>>,    #[account(        init,        payer = forecaster,        seeds=[PollEstimateUpdate::SEED_PREFIX.as_bytes(), poll.key().as_ref(), &poll.num_estimate_updates.to_le_bytes()],        space= PollEstimateUpdate::LEN,        bump,    )]    pub estimate_update: Box<Account<'info, PollEstimateUpdate>>,
  1. Line 13 bump=user.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  2. Line 18 seeds=[Poll::SEED_PREFIX.as_bytes(), &poll.id.to_le_bytes()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 26 close = forecaster: Closes the account at the end of the instruction and refunds its lamports.
  4. Line 30 init,: Creates and pays for a new account. Space and payer must be specified.

How to run, build and test

These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.

DirectoryCommandRuns
.npm run lintprettier */*.js "*/**/*{.js,.ts}" --check
appnpm run devnext dev
appnpm run startnext start
appnpm run buildnext build
appnpm run lintnext lint
.anchor buildcompile programs and generate IDLs
.anchor testlocal validator + test suite
.anchor run testyarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts
.cargo buildRust workspace build
.cargo testRust tests

What you could build from it

Used in lessons