ChitChain is a trust-minimised chit fund on MST Blockchain. Members lock collateral and pay into a pot that lives inside a smart contract. Each round everyone contributes, the contract covers missed payments from the defaulter's collateral, and the round's recipient gets the first choice: take the full pot, or decline so the others can bid in a reverse auction where the lowest payout offer wins and the difference is shared as dividends. Part of early winners' payouts is held back as security for their future dues. An AI risk engine prices each member's collateral (Low 0.5× · Medium 1× · High/Unassessed 2× by default) and an AI bidding agent bids for members based on a plain-language goal. Nobody, not the organiser and not the AI, ever holds the money.
JOIN → CONTRIBUTE → POT READY → ACCEPT the full pot, or DECLINE → AUCTION → SETTLE → DIVIDENDS, with collateral covering any missed payment. Every step is a real MST testnet transaction.
This is a testnet-only prototype. Every amount in the app is in MST testnet coins, which have no monetary value. There is no INR, USD, UPI, bank, card or payment gateway anywhere in the code. The UI shows an MST TESTNET badge wherever a balance appears.
Informal chit circles run on trust in one organiser who holds the pot, keeps the ledger and decides what happens when someone misses a payment. Money goes missing, records are disputed, and defaulters are chased socially rather than by rule.
The pot, every member's collateral and the reserve sit inside ChitChain.sol, not in anyone's account. Contributions, the auction, payouts, dividends, holdbacks and default coverage are rules the contract enforces, so no organiser, backend or AI can move funds. The backend only indexes chain events for the UI and triggers settlement after deadlines; the database is never the source of truth for balances.
MST is an EVM-compatible chain with a public testnet, a faucet, a Blockscout explorer and the BridgeKey wallet, so the whole flow is verifiable by anyone with a browser. The hackathon track required it, and its fast blocks make 30-second demo rounds practical.
Built in 24 h for MST Blockchain × NEWRRO Buildathon 2026 — MST Blockchain Track.
- App: https://frontend-production-d322.up.railway.app
- Backend API: https://backend-production-64738.up.railway.app/health
- Demo video:
<link> - Hosting: frontend, backend, the CrewAI
ai-agentservice and PostgreSQL all run on Railway (deployed with the Railway CLI, no GitHub integration).
- Network: MST Testnet (chain ID
91562037, RPChttps://testnetrpc.mstblockchain.com) - Contract:
ChitChainv2.2 at0x4096bDd55345CD98b4168d70A8595E544eEDCBFd→ https://testnet.mstscan.com/address/0x4096bDd55345CD98b4168d70A8595E544eEDCBFd (deployed at block 5802569, tx0x9fa621a4…b2ce0d). v2.2 adds the recipient decision: the auction only exists after the round's recipient declines the full pot. Earlier contracts stay verifiable: v2.10xFBA432E34E70d6069677d80944A9eEf83376CA98, v20xA18D48c29Bf68B750BB3fCb2f109661C5de1bD45(the older tables below), v10xe53a0C78def8046ce1199D90EB8D2F81bb5A81d5. - What runs on-chain and why: custody of pot + collateral + reserve, contributions, the recipient's accept or decline, the payout-offer auction, settlement, post-win holdback, default coverage, member removal, dividends, reputation counters. Everything that moves money is a contract rule, so it can be verified on MSTScan.
- Wallet: BridgeKey (EIP-1193, Chrome Web Store) · SDK: ethers v6 (the library
@mstblockchain/mst-sdkwraps) · Scaffold structure from MST VibeKit (Hardhat + Next.js) - Faucet: https://faucet.masterstroke.academy (10 MST per address per 24 h)
- Verified facts:
eth_chainIdon the testnet RPC returns0x5752035= 91562037; explorer is Blockscout attestnet.mstscan.comwith/tx/<hash>and/address/<addr>.
| Action | Tx |
|---|---|
Deploy ChitChain v1 |
0x2388ead3…092fc5 |
setRiskTier(D, High) by the risk oracle |
0xeb66b371…be5ccf |
setRiskTier(A, Low) |
0x1e2806f4…2ad2be |
createCircle (5 members, 60 s rounds, 0.1 MST) |
0x914db368…ea506f |
| Join — D (High tier, locks 2× = 0.20 MST) | 0xc2dced24…6e26f3 |
| Join — A (Low tier, locks 0.5× = 0.05 MST) | 0xa9761efc…14f0a4 |
| Agent bid for B ("I need money this month", 5 % of pot) | 0xd73eef9b…781b6d |
settleRound #1 — B wins, D's missed payment covered from collateral, holdback 0.20 applied, pot still 0.50 |
0x05e3c2a1…635826 |
| Withdraw — B pulls 0.27 MST payout | 0x59f853a5…4c9508 |
The brief's five-round story at testnet scale (500 → 0.1 MST, so the offers 480 / 470 / 460 / 450 become 0.096 / 0.094 / 0.092 / 0.090):
| Step | Tx |
|---|---|
createCircle (5 custodial demo wallets, 30 s windows) |
0xe4045bc0…dde091 |
Round 1: PotReady 0.1 MST, recipient A |
0x535a867f…6ac050 |
Round 1: A accepts the full pot (FullPotAccepted + RoundSettled, no auction) |
0x1448d8fe…e4617d |
Round 2: PotReady, recipient B |
0x1bd3917e…3bdef8 |
Round 2: B declines, the auction opens on-chain (FullPotDeclined) |
0x429e904f…d481e8 |
| Round 2 offers: B 0.096 · C 0.094 · E 0.092 · D 0.090 | 0x6577b330…3949c4 · 0x0e6e42f0…84eb29 · 0x0be861ce…22b7df · 0x00df59d7…ee54e5 |
| Round 2 settled: D wins at 0.090; the 0.010 discount is split 0.0025 to A, B, C and E | 0xf8fec637…db0a2f |
| Round 3: C accepts the full pot | 0x5b8703c0…68248e |
| Round 4: D misses; default protection covers 0.02 from D's collateral, pot stays 0.1; recipient E | 0x0e483fde…52f50a |
| Round 4: E accepts | 0x1fb9fa9e…d01c2d |
Round 5: B accepts; CircleCompleted |
0x73650414…e3a859 |
Circle #1 on v2.2 shows the insufficient-collateral path: E started with too little test MST, its collateral covered two misses and it was removed in round 3 with a real shortfall (0x8d458594…e39395).
Rows above the v2.2 table are from the v1 contract's demo circle. The v2 rows (two-phase rounds, DefaultDetected) are added below after the v2 demo run. Every event in the live app links to the same explorer.
| Action | Tx |
|---|---|
Deploy ChitChain v2 |
0x2f2321f3…fe55e5 |
setRiskTier(D, HIGH) — risk score 74 |
0x524d1ca9…0f3a6b |
setRiskTier(A, LOW) — risk score 13 |
0x5a3972c9…e8da86 |
createCircle (5 members, 30 s contributions + 30 s bidding, 0.1 MST, 10 % holdback) |
0x3fd50967…cc4405 |
| Join — D (HIGH, locks 2× = 0.20 MST) | 0x49f83c3e…59660d |
| Join — A (LOW, locks 0.5× = 0.05 MST) | 0xe420c982…4024b4 |
| Agent bid for B (goal "I need money this month", desired payout 0.45 → discount 0.05) | 0xdacf5436…47b7bd |
settleRound #1 — B wins 0.25 MST after 0.20 holdback; DefaultDetected(D) covered 0.10 from collateral, shortfall 0; dividends 0.0125 each |
0x039b272b…f95766 |
settleRound #2 — A wins; D covered from collateral again (collateral now 0) |
0x425ddc61…f5e539 |
settleRound #3 — D partially covered: 0.01 from reserve, shortfall 0.09, D removed; pot 0.41 (shown as "short by 0.09 MST") |
0x550248b8…92ceaf |
Rounds 1–2 show the full-cover case (pot fully funded); round 3 shows the honest partial-cover case once D's collateral was exhausted.
A creator sets: contribution per round, members (2–20), contribution window, decision and auction window (the same length is used for the recipient's decision and, after a decline, for the auction), join window, platform fee (≤ 3 %), base collateral (≥ contribution), holdback %, max discount (≤ 50 %), and the collateral multipliers for Low / Medium / High risk. Nothing is hard-coded per circle.
Required collateral = baseCollateral × multiplier(tier); defaults 0.5× / 1× / 2×. An Unassessed wallet pays the High multiplier, so a fresh Sybil wallet never gets a discount. The tier is snapshotted at join; later oracle updates do not change a running circle.
Every active member pays exactly contribution once per round, before the contribution deadline. The payment that completes the pot closes the contribution phase at once; otherwise anyone (normally the keeper) calls closeContributions after the deadline, which covers every miss (below) and makes the pot ready. Status per member: PAID, PENDING, COVERED_BY_COLLATERAL, PARTIALLY_COVERED, DEFAULTED.
When contributions close, the contract itself checks who has not paid:
required = contribution
if collateral ≥ required: collateral −= required; pot += required → DefaultDetected(shortfall = 0)
else: fromCollateral = collateral; fromReserve = min(gap, reserve)
shortfall = required − fromCollateral − fromReserve
member removed → DefaultDetected(shortfall) + Removed
The keeper only calls closeContributions; the deduction is contract logic and produces one on-chain event with required, fromCollateral, fromReserve and shortfall. The UI says "Pot fully funded" only when shortfall == 0; a partial cover is shown as "Pot short by X MST". Nothing is ever minted to hide a gap.
When the pot is ready (PotReady), the contract names the round's recipient: starting at position (round − 1) mod n in join order, the first member who has not received a pot and is not removed (round 1 → A, round 2 → B, and members who already won are skipped). Only the recipient can call:
acceptFullPot— the round settles immediately with the recipient as winner and discount 0. No auction is created.declineFullPot— the phase changes toAuctionon-chain and the auction window starts.
The recipient cannot accept after declining or decline after accepting (WrongPhase), and nobody else can decide (NotRecipient). If the decision window passes with no choice, anyone can settle and the recipient receives the full pot (outcome DECISION_TIMEOUT).
Members who have not received a pot make a payout offer: how much of the pot they will accept. The contract stores it as a discount (pot − offer). The lowest payout offer (highest discount) wins; an offer must be lower than the current lowest; the discount is capped by maxDiscountBps of the assembled pot. placeBid reverts with WrongPhase while contributing or deciding, and with BiddingClosed after the window. If the auction closes with no offers, the recipient receives the full pot (outcome NO_BIDS).
payout = pot − fee − discount (discount 0 when the full pot is accepted), then the holdback below is applied and the rest is credited to the winner's claimable balance. settleRound is permissionless once the decision or auction window has passed; the keeper does it within a few seconds. Each round's record (getRoundHistory) stores the recipient and the outcome: ACCEPTED, AUCTION, DECISION_TIMEOUT, NO_BIDS or NO_RECIPIENT (nobody eligible left, the pot minus fee is shared as dividends).
dividendsTotal = discount
dividendPerMember = discount ÷ (active members − winner) dust → first eligible member in join order
Both numbers are shown in the Round History table and in every DividendCredited feed item.
tierGap = max(0, remainingDues × coverage(tier) − collateral) coverage: Low 50 % · Medium 75 % · High 100 %
flatHold = payout × holdbackBps / 10000 per-circle, default 10 %
holdback = min(payout, max(tierGap, flatHold))
The held amount stays in the winner's collateral and covers their future contributions; it is released with the collateral at completion. A High-risk winner is always fully secured.
Only the claimable balance (payouts, dividends, refunds, released collateral) can be withdrawn, by the member, via pull payment. Locked collateral, holdback and the reserve can never be withdrawn while a circle is active.
Inputs: on-time rate, missed payments, circles completed, removals, plus labelled synthetic history for the five demo wallets. Score 0 = safest … 100 = riskiest:
riskScore = clamp(60 − 40·onTimeRate − 10·min(completed,3)/3 + 30·(removed > 0), 0, 100)
LOW ≤ 39 · MEDIUM 40–69 · HIGH ≥ 70 · cold start = 40 (MEDIUM)
The LLM only writes a two-sentence explanation from those factors (template fallback if offline). The tier is written on-chain by the risk-oracle wallet with setRiskTier, and the contract, not the frontend, enforces the collateral.
A member gives a goal ("I need about 0.45 MST this round"), optional desired payout, max discount, urgency and risk tolerance. The agent never opens an auction and never decides for the recipient: it waits through contributions and the recipient's decision, and acts only once the recipient has declined and the auction is open on-chain. Then it reads the live pot, lowest accepted payout, time left and rounds left, asks the LLM for {bidThisRound, discountPct, reason}, then applies deterministic guardrails (eligibility, cap, must beat the best bid, member's limits) and a staticCall preflight before sending placeBid from the member's custodial demo wallet. Every decision, including skips, is logged with reason, round, timestamp, wallet and tx hash. It does not guarantee optimal outcomes.
Every 3 s the backend checks each active circle and triggers only the permissionless step whose deadline passed: closeContributions after the contribution deadline, settleRound after the decision or auction window. It can trigger, never decide: it never accepts, declines or bids for anyone.
After a wallet signs in for the first time it is asked for a username (3 to 20 characters, letters, numbers and single underscores, starting with a letter, stored in lowercase). Names are unique, reserved words and platform prefixes (admin, support, demo, mst, chitchain…) are refused, anything that looks like an address is refused, and lookalikes such as rahul and rahu1 count as the same name. The username is an off-chain display name mapped to the wallet; the wallet address stays the on-chain identity and is shown next to the name everywhere. The profile page shows username, wallet, risk tier, circles, completed rounds, contributions, defaults, payouts and dividends, all read from the contract or its indexed events.
"ChitChain operates the platform. The organizer manages the circle. Members participate in the circle. The smart contract controls the financial rules and funds."
| Role | Description | Can | Cannot |
|---|---|---|---|
| MEMBER | Participates in a chit circle | connect wallet, sign in, join, lock collateral, contribute, bid, withdraw claimable balances, view everything about their own position, ask the AI agent (demo wallets) | change rules, withdraw the pot, change anyone's risk, touch another member's collateral, pick a winner, bypass deadlines or default rules |
| CIRCLE ORGANIZER | Creates and manages a circle but does not personally hold the pot | create a circle from their own wallet, name it, invite members, monitor contributions, defaults, bidding and settlement, view analytics and history | transfer the pot, mark someone as paid, deduct collateral, change balances, choose a winner, withdraw another member's funds |
| PLATFORM ADMIN | Operates the ChitChain website and infrastructure | view all circles, users, activity, keeper/indexer health, audit log, support tickets, safe config; suspend website access; run demo controls (custodial demo wallets only) | withdraw or move any member funds, edit blockchain balances. There is no "withdraw all" anywhere. |
| SMART CONTRACT | Controls the financial rules | hold the pot, collateral and reserve; enforce contributions, the auction, settlement, payouts, dividends, default deduction and holdback | be overridden by the backend, organizer or admin |
The deployed contract (ChitChain v2) has no privileged fund functions. Concretely:
createCircleis permissionless; the caller is stored ascreatorand gets no on-chain power over funds. The Organizer role therefore lives in the website database and only gates website screens (naming, invites, analytics).settleRound, which performs the default deduction, is callable by anyone after the bidding deadline. The keeper calls it for convenience; the deduction logic is the contract's.setRiskTieris the only privileged call (the risk-oracle wallet), and it affects only future joins; a circle snapshots each member's tier at join.withdrawpays only the caller's ownclaimablebalance. Locked collateral, holdback and the reserve are never withdrawable while a circle is active.withdrawTreasurypays accumulated fees to the treasury address set at deploy time, never member funds.receive()/fallback()revert, so the contract cannot be used as a wallet by anyone. Because of this, adding Organizer/Admin roles required no contract change: the website cannot give itself powers the contract does not expose.
- Connect BridgeKey (EIP-1193). The app only ever sees your public address.
- The backend issues a one-time nonce (valid 5 minutes) and a plain-text sign-in message that includes the domain, your address, chain id 91562037, the nonce and timestamps.
- You sign that message with
personal_sign. Signing is free and moves no funds. - The backend rebuilds the message from its own stored nonce, verifies the signature with
ethers.verifyMessage, marks the nonce used, and issues a 24-hour session token (HS256 JWT sent as a bearer header). Nonces cannot be replayed; sessions can be revoked by logging out or by an admin suspension; the role is re-read from the database on every request. - You are redirected by role: MEMBER →
/dashboard, ORGANIZER →/organizer, ADMIN →/admin. No passwords, no seed phrases, no private keys: ChitChain never asks for them and never stores them.
Admin fallback: the platform administrator can optionally sign in with a username + password (ADMIN_LOGIN_USER / ADMIN_LOGIN_PASSWORD_HASH, hash generated with npm run hash-password) when their wallet is unavailable; the link appears on /login only when the backend has it enabled.
That password session is an admin:<username> account with no wallet: it grants website admin rights only (users, support, audit, config) and can never sign transactions or control wallets or funds. Members and organizers always sign in with their MST wallet.
The ChitChain platform team runs the website, backend API, database, AI services, keeper and monitoring. Website administrators can see everything and support users, but cannot change blockchain financial state.
The ChitChain smart contract on MST testnet holds every MST in every circle and moves it only through its coded rules: contribution → contract; default → member collateral → pot/reserve; payout → winner's claimable balance; dividend → eligible members' claimable balances. The backend and database only index and cache what the chain already says.
There is no broker role. A real-world deployment would require registered chit operators and regulatory compliance (Chit Funds Act, 1982); the MVP does not claim to provide that.
See ARCHITECTURE.md (behaviour), INTERFACE.md (contract ABI), DESIGN.md (UI) and API.md (REST).
CHITCHAIN PLATFORM (website team)
|
┌────────────────┼────────────────┐
↓ ↓ ↓
Frontend Backend AI
(login, UI) (sessions, API, index, (risk, bids)
keeper, audit, support)
└────────────────┼────────────────┘
↓
Members ─ Organizers ─► MST Blockchain (BridgeKey signs every tx)
↓
ChitChain contract
↓
Pot / Collateral / Rules
Separation of duties: the blockchain is custody + rules; the AI is judgement. The AI's only privileged on-chain power is setRiskTier (risk oracle wallet). The bidding agent bids from custodial demo wallets that are labelled as such in the UI.
Dark midnight theme with the MST red accent (DESIGN.md §14): a 240 px sidebar (icon rail below 1280 px, bottom tabs on phones), a sticky header with the network pill, notifications and wallet menu, and an overview-first layout. Red marks the primary action, the active nav item and chain references; money in the contract is sky, the AI agent is violet. Every amount is MST and labelled testnet, every number is read from the contract or the indexer, and anything unavailable says so instead of showing a placeholder.
| Page | What it shows |
|---|---|
/ |
Hero with the signature money-flow visualisation, network stats, a live ticker of the newest on-chain events, the latest circle, a scroll-driven "How it works" (the pot illustration plays one round as you scroll), why blockchain, transparency, roles and the circles grid |
/dashboard |
Greeting, the one next action for your active chit (pay the contribution, accept or decline your pot, place a bid, or wait for settlement), stat cards, the active chit card, the recipient decision or live auction, money flow, AI risk card and recent transactions |
/circle/:id |
Round timeline (contributions, pot ready, recipient decision, auction only if declined, settlement, dividends) with how the last round ended, pot meter, the recipient decision card (accept the full pot or decline and open an auction, with fee and holdback preview), the auction card with pot, current lowest payout and current discount and the "Place a bid" payout-offer dialog, AI bidding agent panel, members with usernames, round history with outcomes, defaults and the live feed |
/activity |
Transaction centre with type tabs and the 5-step transaction state machine (Preparing, Wallet confirmation, Submitted, Confirming on MST, Confirmed) |
/create, /member/:addr, /collateral, /support |
Two-column create form with live summary; profile with username, stats, the risk card and on-chain history (and username settings for your own wallet); per-circle collateral; help centre and tickets |
/organizer, /organizer/circles/:id, /admin, /demo |
Organizer dashboard (recipient, decision, auction status, lowest payout, discount, defaults, collateral per circle) and per-circle analytics with the round timeline; platform admin (users, audit log, support, config, system health, treasury); demo controls for the custodial wallets |
Motion. Twelve scroll-driven and ambient effects (DESIGN.md §15): reading-progress bar, headline word reveal, hero parallax, pointer-following glow, live ticker, pinned scrollytelling "How it works", self-tracing money-flow connector, self-drawing history rail, spotlight card borders, magnetic buttons, rolling countdown digits and the spinning hero ring. Everything is transforms and opacity, and every effect has a static fallback. An Animations switch in the footer and sidebar cycles System, On, Off per browser: System follows the OS reduce-motion setting, the choice is stored in localStorage and mirrored on <html data-motion> before the first paint. Layout is checked at desktop and phone widths for horizontal overflow, clipped text and console errors.
contracts/ ChitChain.sol · ChitChainBase.sol · IChitChain.sol · test/ReentrantAttacker.sol
test/ ChitChain.test.ts (12 cases from ARCHITECTURE §3.7 + INTERFACE §7 invariants)
scripts/ ping · deploy · export-abi · seed-demo
deployments/ mstTestnet.json (address, deploy tx, block)
backend/ Express + Prisma/PostgreSQL: indexer, keeper, risk engine, bidding agent, demo autopilot
agent/ Python 3.12 + CrewAI + FastAPI: AI bidding crew (decides only; Node guards and executes)
frontend/ Next.js 14 + Tailwind + shadcn/ui + framer-motion (motion primitives in components/motion/)
CLAUDE.md working rules for AI-assisted edits: folder map, house style, commands, deploy
The chain is the source of truth for money; PostgreSQL is a read model plus agent memory. Nothing in the database can move funds. The backend talks to Postgres only through Prisma (backend/prisma/schema.prisma, client in backend/src/db/).
MST Testnet ──events──► indexer ──createMany(skipDuplicates)──► events
▲
frontend /feed, /circles/:id, /members/:addr/risk ◄── Prisma ────┤
│
keeper ── settleRound ──► chain agent ── placeBid ──► chain ──► agent_logs
| Model | Table | Purpose | Key |
|---|---|---|---|
Event |
events |
every contract event, decoded args as JSON, tx hash + block + timestamp | @@unique(txHash, logIndex) makes re-indexing idempotent |
AgentLog |
agent_logs |
each bidding-agent decision (bid or skip), reason, source (llm/fallback), tx hash | indexed by circle |
Mandate |
mandates |
a member's plain-language goal for the agent, optional max discount | @@id(circleId, member) |
BidAgent |
bid_agents |
v4 autonomous bidding strategy per circle + custodial wallet: limits, permission, status, last decision / bid / tx | indexed by circle + status |
AgentEvent |
agent_events |
v4 activity lines of a BidAgent (kind, text, user-facing reason, JSON data), streamed over SSE | indexed by agent + id, cascade delete |
RiskCache |
risk_cache |
cached risk result JSON per address (30 s TTL) | address |
DemoSkip |
demo_skip |
demo console "skip payment" toggle per wallet | address |
DemoCircle |
demo_circles |
circles the demo autopilot manages | circleId |
Meta |
meta |
key/value: last_block (indexer cursor) and contract_address (a changed deployment resets the chain-derived tables) |
key |
Design choices:
- Idempotent ingestion. The indexer polls
queryFilterfromlast_block + 1in ≤ 2,000-block chunks and inserts withcreateMany({ skipDuplicates: true })in one transaction, so restarts never duplicate feed items. - Schema as code. Tables are created from the Prisma schema at boot (
prisma db pushinnpm start), so a fresh Railway Postgres works with zero manual steps. Migrations (prisma migrate) are the next step once the schema stabilises. - Rebuildable. Drop the database and the feed rebuilds from
START_BLOCK. WhenCHITCHAIN_ADDRESSchanges (a redeploy), the indexer wipes the chain-derived tables itself and re-indexes, so circle ids never collide across contracts. Only agent mandates and logs are backend-native state. - Wei stays a string. Amounts are stored as decimal strings and converted to
bigintat the code edge, never as floats.
Local development:
export DATABASE_URL=postgres://postgres:postgres@localhost:5432/chitchain
cd backend && npm run prisma:push && npm run db:smoke # expect OKPrerequisites: Node 20+, npm, a BridgeKey (or any EIP-1193) wallet with test MST from https://faucet.masterstroke.academy.
npm install && (cd backend && npm install) && (cd frontend && npm install)
cp .env.example .env # fill the keys (see below)
npx hardhat test # contract tests
npm run ping # prints chain id + balances on MST testnet
npm run deploy # deploys, writes deployments/mstTestnet.json
npm run export-abi # copies ABI into backend/ and frontend/
(cd backend && npm run prisma:push && npm run dev) # http://localhost:4000 (needs DATABASE_URL)
(cd frontend && npm run dev)# http://localhost:3000Env vars (.env at the repo root; never committed):
| Var | Purpose |
|---|---|
DEPLOYER_PRIVATE_KEY |
deploys the contract, funds demo wallets |
KEEPER_PRIVATE_KEY |
calls settleRound after each deadline |
RISK_ORACLE_PRIVATE_KEY |
calls setRiskTier (constructor riskOracle) |
AGENT_WALLET_KEYS |
5 comma-separated keys for custodial demo members A–E |
TREASURY_ADDRESS |
receives leftover reserve at circle completion |
CHITCHAIN_ADDRESS, START_BLOCK |
from deployments/mstTestnet.json |
DATABASE_URL |
PostgreSQL connection string (Railway injects ${{Postgres.DATABASE_URL}}) |
LLM_API_KEY, LLM_BASE_URL, LLM_MODEL |
any OpenAI-compatible chat API; blank → template fallback |
NEXT_PUBLIC_* |
frontend: contract address, chain id 91562037, RPC, API URL, explorer |
Demo flow: open /demo → Fund wallets → Assess all (D becomes High, A Low) → New demo circle (30 s rounds) → open the room → ask the agent for B "I need money this month" → toggle skip on D → watch the keeper settle, D's collateral cover the miss, and the pot stay full.
- Install BridgeKey from the Chrome Web Store link above and create a wallet.
- Switch it to MST Testnet (the app offers to add the network: chain id 91562037, RPC
https://testnetrpc.mstblockchain.com, explorerhttps://testnet.mstscan.com). - Claim 10 MST at the faucet for your address.
- Open the app, click Connect, approve in BridgeKey. The network pill turns green.
npx hardhat test # 29 contract cases incl. balance invariant after every step
(cd backend && npm test) # 60 cases: risk score, agent guardrails, v4 risk guard + fallback
(cd frontend && npm run typecheck && npx next lint && npm run build) # never run the build while the dev server is upContract cases cover: creation and parameter validation, joining, Low/Medium/High/Unassessed and custom collateral multipliers, correct / wrong / duplicate contributions, valid / too-high / not-higher / non-member / after-deadline bids, winner selection, payout, discount, dividends and dust, default detection with full, partial and reserve-assisted coverage, double-default prevention, holdback (tier floor, flat %, release), withdrawal restrictions, unauthorised oracle/treasury calls, re-entrancy, settlement timing and multi-round completion.
Open /demo (or use the API):
- Fund wallets · 2. Assess all (A LOW, B MEDIUM, C LOW, D HIGH, E MEDIUM, each a
setRiskTiertx) · 3. New demo circle (5 members, 30 s contributions + 30 s bidding, 0.1 MST, 10 % holdback) — the five custodial demo wallets join with 0.05 / 0.1 / 0.05 / 0.2 / 0.1 MST collateral. - In the room, ask the agent for B: "I need money this month". Its bid appears in the feed with a reason.
- Toggle skip on D. At the next settlement the contract emits
DefaultDetectedfor D: 0.1 MST taken from D's collateral, pot still full. The Default event card shows required / used / remaining / status and links to MSTScan. - Open Round history for pot, payout, discount, dividends per member and holdback of every settled round.
An autonomous bidding agent (v4) watches one auction for one custodial demo wallet and bids within the limits the user set. The AI only proposes; a deterministic Risk Guard and the Node backend execute. Experimental, testnet only.
MONITOR ──► ANALYZE ──► DECIDE ──► VALIDATE ──► BID ──► VERIFY ──► CONTINUE
snapshot Auction Bidding Risk Guard placeBid 1 conf. next tick
every 4 s Analyst Strategist (12 checks, from the TX_CON- (or DONE
REFRESH / (CrewAI) (CrewAI) pure, unit- custodial FIRMED when the
BID_SEEN WAIT|BID| tested) wallet + feed round ends)
STOP
Architecture. agent/ is a Python 3.12 FastAPI service built on CrewAI: a BiddingFlow (load_strategy → fetch_auction → analyse → strategise → clamp) with two agents, the Auction Analyst and the Bidding Strategist, whose tools read the Node public endpoints GET /auction/:id and GET /auction/:id/bids. The strategist's output is a Pydantic BidDecision (WAIT | BID | STOP, discount in MST, reason code, reason, confidence), clamped deterministically in Python. The Node backend (backend/src/ai/) runs the loop, asks the crew (POST /evaluate, 20 s timeout), falls back to the same deterministic rules when the crew is unavailable, re-validates every proposal with the Risk Guard, and only then sends placeBid from the custodial demo wallet. Every step is an AgentEvent streamed over SSE to the room (/ai/bidding/stream/:agentId). Full endpoint list in API.md (v4).
Safety.
- The crew has no wallet and no write access; it cannot execute anything.
- Risk Guard, in order:
MAX_BID_EXCEEDED,MAX_DISCOUNT_PCT_EXCEEDED,ABOVE_CONTRACT_MAX,NOT_HIGHER_THAN_BEST,AUCTION_NOT_ACTIVE,WRONG_CIRCLE,WRONG_ROUND,STRATEGY_EXPIRED,AGENT_NOT_ENABLED,WALLET_UNAUTHORIZED,INSUFFICIENT_BALANCE(0.02 MST gas reserve),NOT_ELIGIBLE. - Autonomous bidding is an explicit permission; without it the agent pauses with "Needs your approval" and the user approves each bid.
- Hard caps: maximum discount in MST and in % of pot; a bid is never above them, never above the circle's max, and always beats the current best.
- After 3 failed transactions the agent pauses; expiry and round end finish it. Only custodial demo wallets A–E, only MST testnet, no real funds.
- Activity lines carry short operational text and a user-facing reason, never chain-of-thought. No fabricated bids, hashes or state.
Demo mode. With demoMode on, 25 s after activation (while bidding is open and once per round) another eligible custodial demo wallet places a real small bid on testnet (best + 2 % of pot, capped at 10 % of pot). It appears as "Demo rival (wallet C) bid 0.20 MST" with simulated: false, so the WAIT → new bid → BID story happens on-chain.
Env. Backend: AI_AGENT_URL (URL of the ai-agent Railway service; empty → deterministic fallback only). Agent service (agent/): GROQ_API_KEY, CHITCHAIN_API_URL, MODEL (default groq/qwen/qwen3.8-27b via LiteLLM), PORT. Local run: cd agent && python3.12 -m venv .venv && .venv/bin/pip install -r requirements.txt && GROQ_API_KEY=… CHITCHAIN_API_URL=http://localhost:4000 .venv/bin/uvicorn main:app --port 8000.
The agent was activated for demo wallet A with the goal "I need liquidity before Diwali", desired payout 0.22 MST, maximum discount 0.05 MST (20 %), high urgency, autonomous bidding on, demo rival on. Every row below is a real testnet transaction.
| Step | What happened | Transaction |
|---|---|---|
| Rival bid | Demo rival (wallet B) placed a 0.005 MST discount so the agent had something to respond to | 0x7068…27d8 |
| Crew decision | Auction Analyst + Bidding Strategist (Groq via CrewAI) returned BID 0.0299 MST, reason code DESIRED_PAYOUT, confidence 0.9 |
(off-chain) |
| Risk guard | Deterministic checks passed: below the 0.05 MST cap, below 20 % of the pot, above the current best, correct circle and round, eligible wallet | (off-chain) |
| Agent bid | Wallet A bid a 0.0299 MST discount (payout 0.22 MST), confirmed in block 5792441 | 0x4f47…dc75 |
| Settlement | Keeper settled round 1; wallet A won the pot | 0x3151…3554 |
The agent then reported "You hold the winning bid", waited through the rest of the window, and finished with "Auction ended".
Three circles whose join window closed with only one member were cancelled by the keeper with the permissionless cancel(circleId); the joined member's collateral was refunded to claimable (pull withdrawal).
| Circle | Transaction |
|---|---|
| #1 | 0xb457…6905 |
| #2 | 0xce8c…4a30 |
| #3 | 0xf2fe…3cb0 |
POST /demo/assess-all scored the five custodial demo wallets with the heuristic and the oracle wrote each tier on-chain with setRiskTier.
| Wallet | Tier | Transaction |
|---|---|---|
| A | Low | 0xa488…761a |
| B | Medium | 0x5e42…0b22 |
| C | Low | 0x455d…71ef |
| D | High | 0xf878…095a |
| E | Medium | 0x46ac…1df0 |
| Step | What happened | Transaction |
|---|---|---|
| Treasury | POST /admin/treasury/withdraw sent withdrawTreasury() from the treasury wallet and moved 0.04 MST of accrued fees out of the contract |
0xf3dc…76b2 |
| Demo circle #5 | POST /demo/new-circle now pre-checks funding, creates the circle, responds at once, and the autopilot join queue joined all five demo wallets in the background (5 of 5, no failures) |
0xe969…8588 |
Same autonomous flow as circle #8, now on the current contract: demo rival (wallet A) bid a 0.005 MST discount, the CrewAI crew returned BID 0.0299 MST (reason code DESIRED_PAYOUT), the risk guard passed, and wallet C's bid was confirmed in block 5795839; the agent then held the winning bid until the round settled.
| Step | Transaction |
|---|---|
| Demo rival bid (wallet A) | 0x39ac…a401 |
| Agent bid (wallet C, 0.0299 MST discount, payout 0.22 MST) | 0xe2b7…6511 |
Every action in the UI links to https://testnet.mstscan.com/tx/<hash>. Open the contract page, tab Logs, to see PotReady, FullPotAccepted, FullPotDeclined, BidPlaced, DefaultDetected, HoldbackApplied, RoundSettled and DividendCredited with their decoded arguments. Only hashes returned by the MST network are ever displayed.
- Testnet prototype; MST testnet coins are used only for demonstration and have no monetary value. No real money is involved.
- Risk score is a demo heuristic on synthetic demo history, not a credit bureau score.
- AI bidding is experimental and does not guarantee optimal or profitable decisions.
- Keeper, risk oracle and agent wallets are custodial/centralised in the MVP (backend holds keys). Production: multisig oracle, session keys for the agent.
- Bids are public → last-second sniping possible. Production: commit–reveal.
- Usernames live in the backend database (the wallet is the identity); they are display names, not on-chain records.
- Low/Medium early winners are only partially secured (50 % / 75 % of remaining dues); the gap is backed by the circle reserve (fees + forfeits) up to its balance.
- Removed members forfeit collateral and past contributions to the pool (MVP rule; production refunds minus penalty at the end).
- MST is volatile → production needs a stable-value asset.
- Contract is tested but unaudited. Testnet only. Not "fully decentralised": keeper, oracle and agent are backend wallets.
- A real deployment would need legal and regulatory compliance; ChitChain is not an unregistered real-world chit fund operator.
Prototype on testnet. Chit funds are regulated under the Chit Funds Act, 1982; ChitChain is infrastructure for registered organisers and informal friend circles, not an unregistered chit company.
Built by Vallur Sree Vardhan (@vardhan23v) for the MST Blockchain × NEWRRO Buildathon 2026, MST Blockchain Track.
- Live app: https://frontend-production-d322.up.railway.app
- Source: https://github.com/vardhan23v/chitchain
- Specs: ARCHITECTURE.md · INTERFACE.md · DESIGN.md · API.md
