FA591 Blockchain & DeFi · Stevens Institute of Technology · Spring 2026

CongressChain

Members of Congress must disclose their stock trades, but the filings arrive late, as PDFs, spread across two chambers. CongressChain stores every disclosed trade append-only on a public blockchain and scores each one with a contract that anyone can re-run to get the same number.

Try the score

same formula as SuspicionScorer.sol
0/ 100
0Delay0Committee0Hearing0Amount
Author
Zhenhan Chen
Stack
Solidity 0.8.24 · OpenZeppelin v5 · Foundry · ethers v6
Network
Base Sepolia testnet (chain 84532)
Data
Public STOCK Act filings · demo set of 5 members and 15 trades on chain
01

Why put it on a chain

The data could live in a database. The point is the score.

The STOCK Act gives members 45 days to report a trade, and the penalty for filing late is small. Commercial trackers parse the PDFs, but their scores are private: you cannot check how a number was produced or whether the history was edited.

On a blockchain the ledger is tamper-evident and the scoring function is public code. A trade is written once; its score is computed by the contract at insert time; anyone can call the scorer with the same inputs and verify the result.

FilingsHouse Clerk and Senate eFD disclosures, normalized to JSON
RegistryMembers, chamber, party and committee seats
Ledger + scoreTradeRecord stores the trade and asks SuspicionScorer for its score
FrontendRankings, trade list and a score audit read straight from the chain
02

The score

Four sub-scores from 0 to 100, weighted in basis points that sum to 10,000.

FactorWeight0 when100 when
Disclosure delay15%filed same day≥ 90 days (45-day limit = 50)
Committee jurisdiction30%no related committeesits on a related committee
Proximity to a hearing35%≥ 30 days away≤ 3 days away
Trade size20%< $15,000≥ $1,000,000
function score(uint32 delayDays, uint8 committeeRelevant, uint256 amountUsd, uint32 daysToHearing)
    external view returns (uint8)
{
    uint256 total = ( _delaySubscore(delayDays)       * weights.delay
                    + (committeeRelevant > 0 ? 100 : 0) * weights.committee
                    + _hearingSubscore(daysToHearing)  * weights.hearing
                    + _amountSubscore(amountUsd)       * weights.amount ) / 10_000;
    return uint8(total > 100 ? 100 : total);
}

Weights are owner-settable in this version, with every change emitted as an event; the design hands them to holders of the project's SUN token in a later version. Integer arithmetic means the calculator above matches the contract exactly.

03

Five contracts, 374 lines

Small enough for one reviewer to audit in an hour. 18 Foundry tests, all passing.

ContractRoleAddress (Base Sepolia)
04

Live demo

The original course frontend, reading the deployed contracts through a public RPC. No wallet needed to browse.

This is a testnet proof of concept. Trades are real public disclosures, but where a filing date was missing the demo assumes trade date + 30 days, so the scores illustrate the method and say nothing about any individual.