← Back to home

jerico

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

Problem Statement

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.Executive summaryJerico 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.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.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 & notificationsBackend/event indexer (or The Graph) listens to events and pushes websocket/SSE/Push notifications and updates dashboards in real time.Smart contract architecture (recommended) ComponentsInvoiceFactory (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 IDUse 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.Eventsevent 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);Payment mechanics & zero-custody detailsafeTransferFrom(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.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.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.Backend, indexing & real-time updatesIndexer: 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/:invoiceIdPOST /invoice (if you support off-chain creation + signed anchoring)POST /webhook/chain (for internal listeners)Security & best practicesSmart 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.Gas optimization & cost controlStore 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.Edge cases & business rulesPartial 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.UX & mobile specifics (QR / deep linking)QR payload: https://app.jerico/pay?invoiceId=<id>&network=mainnetWalletConnect 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.Observability, analytics & KPIsTrack: 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.Deployment, testing & CI/CDSmart 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.Compliance & legal considerationsPYUSD 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.Example data modelsInvoice (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 }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.)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).Roadmap & feature ideasShort 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.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.Suggested KPIs to highlight in pitchSettlement 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).Final implementation notes & trade-offsAll 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.

Solution

🏗️ 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 opsuseInvoiceDetails() → read invoiceuseUserInvoices() → batch fetchusePYUSDContract() → approvals & balancesusePaymentValidation() → 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 connectTheming with our color palette⚡ Why Viem over web3.js?Better TS inference~50% smaller bundleNative 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 StrategyPayments: 5sBalances: 10sInvoices: on confirmation🚀 Code Splitting → Vite + React Router lazy() imports✅ Hackathon ResultBuilt 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

Hackathon

ETHGlobal New Delhi

2025

Contributors