AgroVerse is a transparent agricultural supply chain platform that registers produce batches on-chain (Arbitrum Sepolia) with INR-denominated pricing, uses Stripe for off-chain payments, and employs verifier relayers to transfer ownership upon verified payment.
Frontend (React/Vite) → Express Server (Node) → Stripe API + On-Chain Contract
↓ ↓ ↓
wagmi + React Query Relayer Account AgriTruthChain.sol
(Auth, Chain State) (Batch Ops, Verify) (Batch Registry)
- Vite proxy (
vite.config.ts): All/api/*,/create-checkout-session,/webhookrequests forward fromhttp://localhost:8000→http://localhost:3001 - Environment: Client reads
VITE_*vars fromserver/.env(seeenvDirin vite.config.ts)
- INR-Only Pricing On-Chain: All batch prices stored as whole rupees (
uint256 basePriceINR), not paise. Stripe amounts converted to paise (÷100) for API calls. - Relayer-Based Verification: Only the relayer account (from
RELAYER_PRIVATE_KEY) can calltransferOwnershipByVerifier()after Stripe payment confirmed. - Idempotent Webhook Handling: Server tracks
processedSessionsSet to prevent duplicate batch transfers if Stripe webhook retries occur. - Role-Based Auth: Six roles managed in
AuthContext.tsx:farmer,distributor,retailer,consumer,verifier,admin. Each has separate profile pages and protected routes.
# Root
npm install && cd server && npm install && cd ..
# Terminal 1: Frontend (http://localhost:8000)
npm run dev
# Terminal 2: Backend (http://localhost:3001)
npm run server:dev- Deploy
contracts/AgriTruthChain.solto Arbitrum Sepolia (Remix/Foundry) - Set
AGRI_TRUTH_CHAIN_ADDRESSandRELAYER_PRIVATE_KEYinserver/.env - One-time:
POST /api/setup-relayer-as-verifierto make relayer an authorized verifier - Verify:
GET /api/chain-infoshould showisRelayerVerifier: trueandhasBytecode: true
npm run build # Vite build (production)
npm run build:dev # Development build
npm run lint # ESLint check
npm run preview # Preview production bundle locally- Location:
src/components/for reusable,src/pages/for route handlers - UI Pattern: All UI components in
src/components/ui/imported from shadcn (Radix + Tailwind) - Example:
Button,Dialog,Cardfollow shadcn naming and composition - Forms: Use
react-hook-form+@hookform/resolvers(seeConnectWallet.tsx)
- Context:
AuthContext.tsxstores{ role, email, address }in localStorage - Route Protection:
ProtectedRoute.tsxchecksuserand role; redirects to/loginwithroleRequiredstate - Usage: Wrap routes like
<Route element={<ProtectedRoute role="farmer" />}><Route path="..." /></Route>
- Config:
src/lib/wagmi.tssets up wagmi config for Arbitrum Sepolia + injected (MetaMask) connector - Contract Reading: Use
useContractReador fetch via server/api/batch/:id - Contract Writing: Routes through server relayer endpoints to avoid gas on frontend
- Viem Setup (
server/src/index.js):const client = createPublicClient({ chain: arbitrumSepolia, transport }) const wallet = createWalletClient({ account, chain: arbitrumSepolia, transport })
- Read Batches:
client.readContract()with ABI fromserver/src/contract.js - Write Batches:
wallet.writeContract()(relayer signs), thenclient.waitForTransactionReceipt()
- Checkout: Client POSTs batch details to
/create-checkout-session - Amount Conversion: INR whole rupees → paise (×100) for Stripe API; capped at 999,999,999,999 paise
- Webhook:
/webhookendpoint validates Stripe signature, idempotency-checks, then callsconfirmPaymentAndTransfer() - Redirect: Post-payment, Stripe redirects to
/batch?id=...&paid=1&session_id=...
- On-Chain State:
verificationStatusis0(unverified) →1(pending) →2(verified) - Server Functions:
getVerificationStatusChain(batchId)→ read from contractsetVerificationStatusChain(batchId, statusLabel)→ relayer writes
- Verifier Dashboard:
StakeholderDashboard.tsxshows unverified/pending batches; verifiers set status before transfer
- Setup:
src/i18n/index.tsconfigures i18next with 4 languages: en, hi, ta, or (Odia) - Translation Files:
src/locales/{en,hi,ta,or}.json - Usage:
import { useTranslation } from 'react-i18next'→const { t } = useTranslation()→t('key') - Persistence: Language preference stored in localStorage
- React Query:
QueryClientinitialized inApp.tsx, provider wraps entire app - Hooks: Use
useQuery()for GET,useMutation()for POST/PUT - Server: Express endpoints return enriched JSON (e.g.,
currentHolderRole, dates, prices computed from batch state)
src/
components/
ui/ # shadcn components (auto-generated)
Navigation.tsx # Header with i18n switcher
ConnectWallet.tsx # wagmi + role selection
ProtectedRoute.tsx # Route guards
StakeholderDashboard.tsx # Role-specific batch UI
context/
AuthContext.tsx # Auth state + localStorage sync
pages/
Index.tsx # Home/landing
Login.tsx # Role + address login
profiles/ # Farmer/Distributor/Retailer/Consumer detail pages
BatchDetails.tsx # Single batch view + payment UI
Verifiers.tsx # Verifier-only verification UI
lib/
contracts.ts # ABI + address constants
wagmi.ts # wagmi config
addresses.ts # Default test addresses
utils.ts # Utility functions
i18n/
index.ts # i18next setup
locales/
en.json # English translations
server/
src/
index.js # Express app, Stripe + blockchain endpoints
contract.js # ABI + address exports
verificationStore.js # In-memory verification persistence
- Create
src/pages/MyPage.tsx(React component) - Add route in
App.tsx(wrap withProtectedRouteif needed) - Use
useAuth()to check role,useTranslation()for i18n - Fetch data via
/api/*endpoints or hook into wagmi
- In
server/src/index.js, createapp.get/post('/api/...') - Read/write chain state using
client.readContract()/wallet.writeContract() - Use helper functions:
getVerificationStatusChain(),hasContractCode(), etc. - Return JSON; let Vite proxy forward from frontend
- Edit
src/locales/{en,hi,ta,or}.json(keep keys consistent) - Use in component:
const { t } = useTranslation(); t('key') - No rebuild needed; i18n reloads on save
- Update
contracts/AgriTruthChain.sol - Deploy to Arbitrum Sepolia
- Update ABI in
server/src/contract.jsandsrc/lib/contracts.ts - Update function calls in server endpoints or client components
- No Gas on Frontend: Never use
writeContract()client-side; route through/api/*relayer endpoints - Stripe Amount Format: Always multiply INR by 100 (to paise); validate min/max bounds
- Address Validation: Use regex
/^0x[0-9a-fA-F]{40}$/before calling chain - localStorage: Auth user stored as
JSON.stringify({ role, email, address })under key"auth:user" - Vite Env: Client code accesses via
import.meta.env.VITE_*; server readsprocess.envfromserver/.env
- Chain Issues:
GET /api/chain-infoshows contract bytecode, relayer address, verifier status - Webhook Failures: Check
processedSessionsSet; Stripe signature validation in/webhookendpoint - Role-Based Access: Verify
AuthContextlocalStorage has correct role; checkProtectedRouteredirect logic - i18n Missing Keys: Check
src/locales/MASTER_KEYS.mdfor exhaustive key list - Contract Calls: Enable verbose logging in
server/src/index.jsviem operations; check RPC endpoint availability