Jerico: Instant blockchain invoicing with PYUSD, QR payments & global transfers.
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.
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.
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.
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.
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);
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.
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.
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.
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)
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.
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.
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.
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.
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.
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.
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.
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 }
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.)
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).
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.
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.
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).
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.
ποΈ 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