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 directory | Source files |
|---|---|
app | 90 |
programs | 22 |
scripts | 3 |
migrations | 1 |
tests | 1 |
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)
- Rust:
rustc 1.99.1 (58cf8f9c3 2026-10-13) - Solana CLI (Agave):
solana-cli 1.18.17 (src:c027cfc3; feat:4215500110, client:Agave) - Anchor (via avm):
0.30.1 - Node.js:
v24.22.1 - npm:
11.21.1 - pnpm:
10.34.6 - Go:
go version go1.27.3 linux/amd64
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 29a7c77667bc68d4bdf2b1cc7a98056af7f8437d3. Configure
No environment file is needed.
4. Install dependencies
| Directory | Command | Result | Time |
|---|---|---|---|
. | npx -y yarn@1 install --ignore-engines --network-timeout 600000 | ✅ pass | 4s |
app | npx -y yarn@1 install --ignore-engines --network-timeout 600000 | ✅ pass | 12s |
. | npx -y yarn@1 install --ignore-engines --network-timeout 600000 | ✅ pass | 1s |
5. Build
| Directory | Command | Result | Time |
|---|---|---|---|
. | RUSTUP_TOOLCHAIN=nightly-2025-04-01 anchor@0.30.1 build | ✅ pass | 143s |
app | npx -y yarn@1 run build | ✅ pass | 47s |
6. Test
| Directory | Command | Result | Time |
|---|---|---|---|
. | RUSTUP_TOOLCHAIN=nightly-2025-04-01 anchor@0.30.1 test --skip-build --provider.cluster localnet --provider.wallet ~/.config/solana/id.json | ✅ pass | 12s |
7. Deploy to a local validator
solana-test-validator --reset # separate terminal
solana config set -ul && solana airdrop 100
anchor deploy --provider.cluster localnet| Directory | Command | Result | Time |
|---|---|---|---|
. | 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 | ✅ pass | 8s |
Proof: program IDs and transactions
poe:6E4atN4EvrvxNtpBQwPMg7AkzJ2TjZrbxBkou9BN99p5(local validator)
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:
poe2pS5g4H8HKyh9jPuQQbm…zidcoiCd
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.comHow it works
Every instruction handler in the program, with the accounts struct it validates. Each link points to the line at the pinned commit.
| Instruction | Accounts | Where |
|---|---|---|
initialize | Initialize | programs/poe/src/lib.rs:17 |
register_user | RegisterUser | programs/poe/src/lib.rs:21 |
remove_estimate | RemoveEstimate | programs/poe/src/lib.rs:70 |
resolve_poll | ResolvePoll | programs/poe/src/lib.rs:74 |
collect_points | CollectPoints | programs/poe/src/lib.rs:78 |
Extend it
- Add an integration test that runs the main flow end to end and asserts every account state change.
- Add a negative test: call each instruction with the wrong signer or account and check that it fails.
- Add an event (
emit!) to each state-changing instruction and subscribe to it from the client. - Replace any unchecked
AccountInfowith typed accounts plushas_one/seedsconstraints. - Make the frontend read the cluster from an env var, deploy it to GitHub Pages and point it at your devnet program.
- 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.
programs/poe/src/contexts/register_user.rs L1–24 @ 29a7c77 · MIT · originaluse 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- Line 4
token::{mint_to, Mint, MintTo, Token, TokenAccount},: Mints new tokens. The mint authority must sign. - Line 12
pub payer: Signer<'info>,: Requires this account to have signed the transaction. - Line 14
init,: Creates and pays for a new account. Space and payer must be specified. - 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. - Line 18
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
programs/poe/src/contexts/make_estimate.rs L13–36 @ 29a7c77 · MIT · originalpub 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- Line 15
pub forecaster: Signer<'info>,: Requires this account to have signed the transaction. - 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. - Line 19
bump=user.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 29
init,: Creates and pays for a new account. Space and payer must be specified. - 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.
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,- Line 13
pub forecaster: Signer<'info>,: Requires this account to have signed the transaction. - 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. - Line 17
bump=poll.bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - 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. - Line 36
init,: Creates and pays for a new account. Space and payer must be specified.
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>>,- Line 13
bump=user.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - 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. - Line 26
close = forecaster: Closes the account at the end of the instruction and refunds its lamports. - 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.
| Directory | Command | Runs |
|---|---|---|
. | npm run lint | prettier */*.js "*/**/*{.js,.ts}" --check |
app | npm run dev | next dev |
app | npm run start | next start |
app | npm run build | next build |
app | npm run lint | next lint |
. | anchor build | compile programs and generate IDLs |
. | anchor test | local validator + test suite |
. | anchor run test | yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts |
. | cargo build | Rust workspace build |
. | cargo test | Rust tests |
What you could build from it
- Fork the SuperStack copy and extend it within the Other gaps listed on the idea page.
- Learn the pattern first: Program Derived Addresses (PDAs)
- Learn the pattern first: Cross-Program Invocations (CPIs)