Search projects, hackathons, sponsors and tech

    ← All projects

    jerico

    Jerico: Instant blockchain invoicing with PYUSD, QR payments & global transfers.

    Demo

    About

    Jerico — Full technical & product deep-dive 🚀 Below is a thorough, structured description of Jerico: what it is, how it works, architecture choices, edge cases, implementation suggestions, and next steps. I’ve included concrete examples, code patterns, data models, and trade-offs so you (or judges/engineers) can act on it immediately. 1) Executive summary Jerico is a mobile-first, blockchain-native invoicing + payment platform that uses on-chain smart contracts and PYUSD to enable instant, peer-to-peer invoice settlement with zero platform custody. Freelancers and businesses create invoices (on-chain or hybrid), share links/QR codes, and clients approve & pay directly in PYUSD. The system provides real-time payment visibility, global accessibility, gas- and storage-optimized contracts, and can reduce payment costs by up to ~90% vs legacy rails. 2) Core value proposition (concise) Instant settlement on approval (no multi-day bank delays). Very low fees (direct token transfer). Transparent, auditable ledger of invoice lifecycle. Mobile-first UX: QR & deep links for one-tap payments. Zero custodial risk — platform never holds funds. 3) High-level user flows (step-by-step) A. Invoice creation (issuer / freelancer) Fill invoice form (client, items, total, due date, metadata, optional payer address). Optionally upload full invoice PDF/metadata to IPFS/Arweave; store CID or hash. Submit → a transaction to InvoiceFactory.createInvoice(...) OR server stores a signed invoice and pins metadata to IPFS then pushes an on-chain record (hybrid). User gets invoice ID, shareable payment link and QR code. B. Client payment (payer) Client scans QR or clicks payment link → opens DApp/wallet. DApp shows invoice details, token (PYUSD), exact on-chain amount (smallest units). Client approves allowance (ERC20 approve) or uses permit (EIP-2612) to allow contract to transfer. Client triggers payInvoice(invoiceId) which performs an atomic safeTransferFrom(payer, issuer, amount). Contract emits InvoicePaid event → frontends and backend listeners update status to Paid. C. Visibility & notifications Backend/event indexer (or The Graph) listens to events and pushes websocket/SSE/Push notifications and updates dashboards in real time. 4) Smart contract architecture (recommended) Components InvoiceFactory (main contract / registry) Creates invoice records (stores minimal on-chain data + metadata hash/CID). Emits events: InvoiceCreated, InvoicePaid, InvoiceCanceled, InvoiceExpired. Facilitates payInvoice by calling IERC20(token).safeTransferFrom(payer, issuer, amount) — this moves tokens directly from payer to issuer (no platform custody). (Optional) InvoiceProxy — if you want per-invoice contract objects (more expensive; generally unnecessary). Use OpenZeppelin libraries (SafeERC20, ReentrancyGuard, Ownable) and uint packing to save gas. Invoice struct (gas-optimized example) Store minimal fields on-chain to reduce gas; put full details off-chain (IPFS). struct Invoice { address issuer; // 20 bytes address payer; // 20 bytes (optional = address(0) for any payer) address token; // 20 bytes (PYUSD contract) uint96 amount; // smallest units (sufficient for typical invoice amounts) uint32 dueDate; // unix timestamp uint32 createdAt; // unix timestamp uint8 status; // enum: 0=Pending,1=Paid,2=Expired,3=Canceled bytes32 metadataCID; // IPFS CID hash or keccak256(metadata) (optional) } Invoice ID Use bytes32 invoiceId = keccak256(abi.encodePacked(issuer, payer, token, amount, dueDate, nonce)); Store mapping mapping(bytes32 => Invoice) invoices; Emit InvoiceCreated(invoiceId, ...) so indexers can find it. Events event InvoiceCreated(bytes32 indexed invoiceId, address indexed issuer, address indexed payer, address token, uint256 amount, uint256 dueDate); event InvoicePaid(bytes32 indexed invoiceId, address indexed payer, uint256 amount, uint256 paidAt); event InvoiceCanceled(bytes32 indexed invoiceId, address indexed issuer); event InvoiceExpired(bytes32 indexed invoiceId); 5) Payment mechanics & zero-custody detail safeTransferFrom(payer, issuer, amount): Contract initiates transfer; token contract debits payer and credits issuer. Token never sits in a Jerico-controlled account — no custody. Allowance requirement: payer must approve InvoiceFactory for amount. To reduce friction, support: EIP-2612 permits (signatures + permit) — single-tx approval + pay. Meta-transactions / relayers that pay gas on behalf of payer (for UX), optionally with gas sponsorship. Edge tokens: For fee-on-transfer tokens, verify recipient’s received delta or disallow such tokens if exact amount required. 6) PYUSD specifics (precision handling) PYUSD has 6 decimals (unlike ETH with 18). On-chain amounts are in smallest units where 1 PYUSD = 10⁶ units. Example: 12.345 PYUSD → 12.345 * 10^6 = 12,345,000 (on-chain integer). Example: 123.45 PYUSD → 123.45 * 10^6 = 123,450,000. Frontend must use ethers.utils.parseUnits(amountString, 6) to convert user input to an on-chain BigNumber. 7) Frontend architecture & UX (React 19 + TypeScript) Stack: React 19 + TypeScript, Vite, TailwindCSS, RainbowKit + Wagmi, Framer Motion. Wallet flow: Use Wagmi connectors: Injected (MetaMask), WalletConnect, Coinbase Wallet. Detect network; prompt chain-switch if needed (Sepolia for demo, Mainnet for production). Payment page responsibilities: Show issuer info, line items (pull from IPFS if off-chain), due date, and exact PYUSD amount. Show payer balance and allowance; provide a one-click Approve & Pay path using permit if token supports it (or show two-step Approve then Pay). QR code generation: generate link https://app.jerico/pay?invoiceId=<id>&network=ethereum and create QR; use qrcode.react or qrcode libs. Deep-link for mobile wallets: walletconnect deeplink or metamask://dapp/… fallback. 8) Backend, indexing & real-time updates Indexer: Listen to chain events (Alchemy/Infura/websocket provider). On InvoiceCreated/InvoicePaid, index to MongoDB for fast search and dashboards. Subgraph: Offer a Graph Protocol subgraph for powerful querying and history. Real-time transport: Use WebSockets / SSE / Firebase / Pusher to notify frontends. Storage: On-chain: minimal invoice record and events. Off-chain: invoice PDFs, metadata, line items (IPFS/Arweave + DB for indexing). APIs (example): GET /invoices?org=...&status=...&page=... GET /invoice/:invoiceId POST /invoice (if you support off-chain creation + signed anchoring) POST /webhook/chain (for internal listeners) 9) Security & best practices Smart contract: Use ReentrancyGuard for payment paths. Use SafeERC20.safeTransferFrom. Follow checks-effects-interactions pattern. Validate dueDate > block.timestamp and amount > 0. Emit events for all state transitions. Limit metadata size stored on-chain; store large files off-chain. Frontend: Validate monetary inputs with precise decimal handling. Protect against phishing: canonical DApp domain, verify invoice signer (optional). Operational: Monitor for suspicious invoice creation patterns (fraud). Maintain immutable logs and retain IPFS CIDs. Audits: Formal smart contract audit (recommended) before mainnet. 10) Gas optimization & cost control Store minimal fields on-chain; use bytes32 metadata hashes instead of strings. Pack storage variables to consume fewer slots. Emit events with rich data instead of writing large structs to storage. Use uint96/uint128 for amounts instead of uint256 where safe. Consider meta-transactions to move gas burden away from payer or issuer depending on UX decisions. 11) Edge cases & business rules Partial payments: Decide whether to allow partial payments or require full settlement. Overpayments: Refund logic or auto-credit feature — must handle safely. Expired invoices: either allow late payments with penalty or reject; status Expired. Disputed invoices: off-chain dispute resolution flows and ability to lock pay action during dispute. Transfer-fee tokens: detect tokens that reduce amount upon transfer; either disallow or adjust expected amount & show warning to payer. Cancellation: issuer can cancel only if not yet paid. 12) UX & mobile specifics (QR / deep linking) QR payload: https://app.jerico/pay?invoiceId=<id>&network=mainnet WalletConnect flow: open dApp in mobile wallet with the invoice page; use wagmi + rainbowkit to handle connect states. PWA support: add to homescreen, offline invoice viewing. One-tap share: share invoice link via SMS/WhatsApp/email. 13) Observability, analytics & KPIs Track: time-to-settlement, % on-time payments, total settlement volume (PYUSD), user growth, average fee saved (vs typical card or processor fees). Dashboards: total outstanding invoices, paid/expired counts, per-customer volume. Alerts: failed payments, contract upgrade attempts, unusual spikes. 14) Deployment, testing & CI/CD Smart contracts: develop with Hardhat or Foundry. Unit tests, integration tests, fuzz tests, gas reports. Frontend: Vite + TypeScript; test with Playwright / Cypress for flows (connect wallet, approve + pay via local signer). CI: run tests, static analysis (Slither), gas usage reports, and pipelines to deploy to Sepolia then mainnet on approval. Demo environment: Sepolia testnet + seeded PYUSD test tokens for hackathon/demo. 15) Compliance & legal considerations PYUSD is a stablecoin; legal/regulatory obligations vary by jurisdiction. Consider: Anti-Money Laundering (AML) / KYC for higher volumes. Terms of service for disputes/refunds. Local money-transmission laws — consult counsel. 16) Example data models Invoice (off-chain indexed record) { "invoiceId": "0xabc123...", "issuer": "0xIssuerAddress", "payer": "0xPayerAddress", "token": "0xPYUSDAddress", "amount": "123450000", // in PYUSD smallest unit (6 decimals) "amountHuman": "123.45", "dueDate": 1735689600, "status": "Pending", "metadataCID": "Qm... (IPFS)", "createdAt": 1695861234 } 17) Minimal smart contract snippet (illustrative) // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract InvoiceFactory is ReentrancyGuard { using SafeERC20 for IERC20; enum Status { Pending, Paid, Expired, Canceled } struct Invoice { address issuer; address payer; address token; uint96 amount; // smallest units (PYUSD: 6 decimals) uint32 dueDate; uint32 createdAt; uint8 status; bytes32 metadata; // optional } mapping(bytes32 => Invoice) public invoices; event InvoiceCreated(bytes32 indexed id, address issuer, address payer, address token, uint256 amount, uint256 dueDate); event InvoicePaid(bytes32 indexed id, address payer, uint256 amount, uint256 paidAt); event InvoiceCanceled(bytes32 indexed id); function createInvoice(address payer, address token, uint96 amount, uint32 dueDate, bytes32 metadata) external returns (bytes32) { require(amount > 0, "zero amount"); require(dueDate > block.timestamp, "invalid dueDate"); bytes32 id = keccak256(abi.encodePacked(msg.sender, payer, token, amount, dueDate, block.timestamp)); require(invoices[id].issuer == address(0), "exists"); invoices[id] = Invoice(msg.sender, payer, token, amount, dueDate, uint32(block.timestamp), uint8(Status.Pending), metadata); emit InvoiceCreated(id, msg.sender, payer, token, amount, dueDate); return id; } function payInvoice(bytes32 id) external nonReentrant { Invoice storage inv = invoices[id]; require(inv.issuer != address(0), "not found"); require(inv.status == uint8(Status.Pending), "not pending"); require(block.timestamp <= inv.dueDate, "expired"); if (inv.payer != address(0)) require(msg.sender == inv.payer, "not payer"); inv.status = uint8(Status.Paid); IERC20(inv.token).safeTransferFrom(msg.sender, inv.issuer, inv.amount); emit InvoicePaid(id, msg.sender, inv.amount, block.timestamp); } function cancelInvoice(bytes32 id) external { Invoice storage inv = invoices[id]; require(inv.issuer == msg.sender, "only issuer"); require(inv.status == uint8(Status.Pending), "not pending"); inv.status = uint8(Status.Canceled); emit InvoiceCanceled(id); } } (This is illustrative — production needs tests, fee/edge token handling, permit support, and audit.) 18) Integrations & ecosystem (recommended) Wallets: MetaMask, Rainbow, Coinbase, WalletConnect. Providers: Alchemy, Infura, QuickNode for event websockets. Indexing: The Graph or custom indexer with MongoDB. Storage: IPFS / Arweave for invoices & attachments. Analytics/notifications: Pusher/Firebase/Sentry for errors and notifications. Accounting: Integrations with QuickBooks/Xero (webhooks / CSV export). 19) Roadmap & feature ideas Short term: Sepolia demo, permit approvals, QR + link UX polish, The Graph subgraph. Medium term: meta-transactions/gas sponsorship, partial payments, refunds, batch invoicing. Long term: FIAT on/off ramps, recurring invoices, multi-sig / escrow support, advanced dispute resolution. 20) Hackathon/demo checklist (quick) Deployed InvoiceFactory on Sepolia. Simple React demo: create invoice, show QR, pay from another wallet. Real-time front-end update via websockets after InvoicePaid. Metrics panel: volume, average settlement time, cost saved example. Clear README + short video walkthrough. 21) Suggested KPIs to highlight in pitch Settlement time (seconds vs days). Average cost saved per transaction (%) — compute example vs 3% card fee. USD-volume settled on-chain in demo. Number of invoices processed in demo (scale tests). 22) Final implementation notes & trade-offs All on-chain vs hybrid: storing full invoice on-chain is simple but expensive. Hybrid (hash/CID on-chain + full data on IPFS + indexer for search) is recommended for cost efficiency. Permit vs Approve UX: permit reduces an extra approval tx but requires token support. For PYUSD check if permit is supported — otherwise two-step approve remains necessary. Gasless UX: meta-transactions improve new-user onboarding but add infrastructure and cost for relayer sponsorship. Regulation: operating a global payments platform may expose you to AML/KYC obligations — get legal review.

    How it's made

    🏗️ Architecture & Technology Integration 🎯 Core Tech Stack ⚛️ Frontend: React 19 + TypeScript, Vite 7.x, TailwindCSS 4.x, Framer Motion 🌐 Web3 Stack: Wagmi 2.x, Viem 2.x, RainbowKit 2.2.8, TanStack Query 🧠 Custom Hook Layer: useInvoiceContract() → write ops useInvoiceDetails() → read invoice useUserInvoices() → batch fetch usePYUSDContract() → approvals & balances usePaymentValidation() → unified pre-check (balance, allowance, approval) 🔥 Key Technical Highlights 💰 PYUSD Decimals Handling → 6 decimals (vs 18 standard) export function parsePYUSDAmount(amount: string): bigint { return parseUnits(amount, 6); } 🔐 Invoice ID Generation → Keccak256 over multiple params + block.timestamp & block.number 📱 Responsive Animations → Window width tracking + dynamic padding ⚡ Real-time Polling → Payment status (5s), balances (10s), invoices (on tx confirm) 🎭 Complex Navbar Animation → Two-stage (drop-in → expand) 📋 Clipboard API w/ Fallback → Works even on older browsers 🔄 Approval State Machine → Balance check → Approve → Pay → Confirm 🚀 Creative Solutions 📊 TanStack Query + Wagmi Sync → Auto refetch balances, invoices & status post-payment 🔗 ABI Management → Single source of truth + TypeScript inference 📱 QR Code Integration → Mobile wallet payments via scan 🌈 RainbowKit Benefits: Zero-config wallet support (20+) Mobile-optimized QR connect Theming with our color palette ⚡ Why Viem over web3.js? Better TS inference ~50% smaller bundle Native async/await 🔧 Hacky but Effective 🎨 Dynamic CSS-in-JS Animations → Responsive padding w/ Framer Motion 📏 BigInt Token Math → Safe handling of token precision ⏰ Unix Timestamp Conversion → Milliseconds ↔ Seconds pitfalls solved 📦 Performance Wins 🔄 Intelligent Polling Strategy Payments: 5s Balances: 10s Invoices: on confirmation 🚀 Code Splitting → Vite + React Router lazy() imports ✅ Hackathon Result Built a production-ready Web3 invoice system within hackathon constraints: 🧩 Coordinated blockchain state ↔ UI updates in real-time 🔒 Maintained full TypeScript type safety ⚡ Optimized UX with aggressive caching, smooth animations, and responsive design