A donation platform that scores every charitable transaction for fraud risk before it settles, and enforces the same threshold twice — once in the API, once on-chain. Charities are checked against an imported IRS Exempt Organizations extract; donations whose score crosses the flag threshold are held by the smart contract until an owner releases them.
- Weighted risk scoring — a deterministic rule engine in
transactionController.detectFraudcombines EIN validity, IRS presence, amount anomaly, donor wallet history, charity-name pattern matching and transaction velocity into a 0–1 score. - EIN verification against imported IRS data —
verificationServicelooks the EIN up in theirsorgscollection and derives a trust score from IRS status, deductibility and foundation codes. - Trust levels that move with behaviour —
updateTrustMetricsonly reacts once a charity has 10+ transactions, then promotes towhitelistedor drops toblacklistedbased on completion ratio and average fraud score. The 10-transaction floor exists so a single donation cannot whitelist an unknown organisation. - On-chain escrow for flagged donations — the contract transfers funds immediately when the score is below the flag threshold and holds them otherwise, so the fraud decision has an effect that cannot be undone by editing the database.
- Token auth with rotation — access token plus a stored refresh token (
RefreshTokenmodel) and aTokenBlacklist, with Google OAuth as an alternative sign-in. - Layered HTTP hardening — helmet with an explicit CSP,
express-mongo-sanitize,xss-clean,hpp, a general rate limiter on/apiand a stricter one on/api/transactions. - Admin surface — flagged transaction review, user suspension/ban, activity logs with chart aggregation, export endpoints, and Recharts analytics in the React UI.
React (CRA + MUI) Express API Ethereum
───────────────── ─────────── ────────
Donate / Search ──POST /api/transactions──> detectFraud()
| weights: EIN .35, IRS .25,
| amount .15, new wallet .10,
| name pattern .09, velocity .06,
| recipient .20 (capped at 1.0)
v
score >= 0.65 -> "flagged"
|
useBlockchain (MetaMask) ──makeDonation(charity, name, EIN, score*100)──>
CharityGuardDonations
<= 30 VERIFIED, funds sent
< 65 PENDING, funds sent
>= 65 FLAGGED, funds held
until verifyDonation()
MongoDB: Transaction · Nonprofit · IRSOrg · User · RefreshToken · TokenBlacklist · ActivityLog
| Directory | Contents |
|---|---|
backend/controllers/ |
Fraud engine, transaction/auth/user/nonprofit handlers |
backend/services/ |
verificationService (EIN + trust), analyticsService, blockchainService |
backend/middleware/ |
auth, security, validation, errorHandler |
backend/scripts/ |
importIrsData.js, seed-admin.js, demo/dataset loaders |
blockchain/ |
CharityGuardContract.sol — a single self-contained contract |
charityguard-frontend/src/ |
Pages, components, useBlockchain, useAdminAuth, MUI theme |
There is no .env.example committed; the variables below are the ones the code reads.
# 1. API
cd backend
npm install
cat > .env <<'EOF'
NODE_ENV=development
PORT=3001
MONGODB_URI=mongodb://localhost:27017/charityguard
JWT_SECRET=<at least 32 characters>
JWT_EXPIRES_IN=7d
FRONTEND_URL=http://localhost:3000
# optional: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, STRIPE_SECRET_KEY,
# RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX_REQUESTS, LOG_LEVEL
EOF
ADMIN_PASSWORD=<8+ chars> npm run seed:admin # creates the first admin account
npm run dev # nodemon, http://localhost:3001
# 2. Web app
cd ../charityguard-frontend
npm install
cat > .env <<'EOF'
REACT_APP_API_URL=http://localhost:3001
REACT_APP_API_BASE_URL=http://localhost:3001/api
REACT_APP_CONTRACT_ADDRESS=<deployed contract address>
REACT_APP_INFURA_KEY=<infura project id, used to add Sepolia to MetaMask>
EOF
npm start # http://localhost:3000MONGODB_URI is validated by Joi at startup, but config/db.js and the standalone scripts also
accept MONGO_URI; scripts/importIrsData.js reads MONGO_URI only.
IRS data import. Place the IRS Exempt Organizations Business Master File CSVs in
backend/data/irs/, then run node scripts/importIrsData.js. It streams in batches of 500,
tolerates duplicate EINs, and creates a unique index on ein at the end. The script requires
csv-parser, which is not in package.json — install it first.
Contract. blockchain/CharityGuardContract.sol is plain Solidity 0.8.19 with no Hardhat or
Foundry config in the repo. Compile and deploy it with your own toolchain, then put the address in
REACT_APP_CONTRACT_ADDRESS.
Selected routes; auth is a bearer access token unless marked public.
| Method | Endpoint | Notes |
|---|---|---|
GET |
/health |
200 only when Mongo is connected, otherwise 503 |
POST |
/api/auth/register · login · refresh · logout · google |
Rate-limited; refresh tokens rotate |
GET |
/api/auth/me |
Current user |
POST |
/api/transactions |
Runs the fraud engine and persists the result |
GET |
/api/transactions · /flagged · /:hash · /:id/details · /donor/:address |
Paginated and sortable where listed |
GET |
/api/transactions/stats/fraud · /export |
Aggregates and export |
PATCH |
/api/transactions/:id/status · POST /bulk-update |
Review actions |
GET |
/api/nonprofits/search?q=&state=&limit= · /stats · /:id |
Public IRS lookup |
GET/POST |
/api/nonprofits/registered |
List public, register authenticated |
PATCH |
/api/nonprofits/registered/:id/status · /trust · POST /flag |
Admin only |
GET/PATCH |
/api/users, /:id/status, /:id/details, /export |
Admin only |
GET/PUT |
/api/users/wallet/:address (+ /donations, /favorites/:ein) |
Public wallet profile |
GET/POST |
/api/activity-logs (+ /recent, /stats?days=) |
Authenticated; write is admin |
Submitting a donation for scoring:
curl -X POST http://localhost:3001/api/transactions \
-H 'Content-Type: application/json' \
-d '{ "transactionHash": "0xabc123", "nonprofitName": "Example Relief Fund",
"nonprofitEIN": "53-0196605", "donorAddress": "0x742d35Cc...",
"recipientAddress": "0xCharityWallet", "amount": 0.75, "blockNumber": 18456789 }'The response carries fraudScore, the list of riskFlags that fired, and an aiAnalysis object
explaining each one in prose — the engine is rule-based, so every score decomposes into the
checks that produced it.
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript 4.9, MUI 5, Recharts, ethers 6, Transak SDK, CRA |
| Backend | Node.js ≥18, Express 4, Mongoose 8, Joi, Winston |
| Contract | Solidity 0.8.19 |
| Auth / payments | JWT with refresh rotation, Google OAuth2, bcryptjs, Stripe |
cd backend
npm test # jest --coverage
npm run lint # eslintbackend/tests/ holds a Supertest integration suite for registration, login, logout and health,
plus unit tests for the analytics risk bands, insight generation and the fraud engine's
new-donor and amount-outlier paths. The GitHub Actions workflow runs a node --check syntax
sweep, ESLint and npm audit; it does not currently run the test suite.
MIT