Cross-border stablecoin invoice and payment platform built on Arc Network.
Businesses create invoices denominated in USDC or EURC and share a payment link. Payers settle on-chain in three steps — connect wallet, approve tokens, fund escrow — with no intermediaries and sub-second finality.
Live app: arc-network-research.replit.app
- Invoice creation — title, amount, currency (USDC/EURC), recipient address, due date, optional privacy mode
- Shareable pay links —
/pay/:idcheckout page with guided 3-step on-chain flow and live transaction links - Escrow settlement —
approve → fundEscrow → releaseEscrowwith configurable dispute window - Invoice NFT — each paid invoice mints an ERC-721 receipt to the payer's wallet
- Privacy mode — amount hidden on the public pay page; stored off-chain only
- PDF export — download any invoice as a formatted PDF, in-browser
- Analytics dashboard — volume charts, currency split, payment status breakdown (Recharts)
- Protocol revenue tracker — live on-chain
FeeCollectedevent feed from ArcScan - CCTP cross-chain payments — payers on Ethereum, Avalanche, or Base can pay via Circle's Cross-Chain Transfer Protocol
- Mainnet readiness checklist — interactive pre-flight page at
/mainnetwith live RPC check and copyable deploy commands
Written in Solidity 0.8.20, deployed with Hardhat. No external oracle or bridge dependency for the core payment flow.
Payer
│
├─ ERC-20.approve(PaymentEscrow, amount)
├─ PaymentEscrow.fundEscrow(invoiceId) ← pulls tokens, holds in escrow
└─ PaymentEscrow.releaseEscrow(id) ← sends net to payee, fee to treasury, calls markPaid
│
InvoiceRegistry.markPaid(id)
InvoiceNFT.mint(payer)
- InvoiceRegistry — stores invoice metadata, enforces state machine (Pending → Paid/Cancelled)
- PaymentEscrow — holds funds, deducts 30 bps protocol fee on release, emits
FeeCollected - InvoiceNFT — ERC-721 receipt minted to payer on settlement
- Protocol fee — 30 bps (0.30%), configurable by owner via
setFee(feeTo, feeBps), hard-capped at 500 bps
| Contract | Address |
|---|---|
| InvoiceRegistry | 0x64D160b7E91e78e52dFc0e8829640E32A919164C |
| PaymentEscrow v3 | 0xAc6F84B255910D95efC4E90D6f1dd5A3e7FCe7e2 |
| InvoiceNFT | 0x6F80056564491425273A6a481EcC5DAea9D57f23 |
| USDC | 0x3600000000000000000000000000000000000000 (native system contract) |
| EURC | 0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a |
Explorer: testnet.arcscan.app
Faucet: faucet.circle.com — select Arc Testnet (20 USDC / 2 hrs)
| Contract | Address |
|---|---|
| InvoiceRegistry | 0xa77710C5d06A829809b8149C602C71654a9F40bD |
| PaymentEscrow | 0xC45A6938Fd656Ed48021A6C611396BF18b3C8EaA |
| InvoiceNFT | 0x1e7e5166029e287ca6264456b88d200d1118289D |
| USDC | 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 (Circle) |
| EURC | 0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4 (Circle) |
Contracts will be deployed when Arc Mainnet is live. See the Mainnet Readiness page in the app for the pre-flight checklist.
| Layer | Technology |
|---|---|
| Frontend | React 19, Vite 7, Tailwind CSS v4, Wouter, TanStack Query |
| On-chain | viem 2.x, MetaMask / EIP-1193 |
| API | Express 5, Node.js 24 |
| Database | PostgreSQL, Drizzle ORM |
| Validation | Zod v4, drizzle-zod |
| Contracts | Hardhat 2.28, Solidity 0.8.20, ethers v6 |
| Charts | Recharts |
| jsPDF | |
| Monorepo | pnpm workspaces, TypeScript 5.9 |
| API codegen | Orval (OpenAPI → React Query hooks + Zod schemas) |
├── artifacts/
│ ├── arcpay/ # React + Vite frontend
│ └── api-server/ # Express API
├── contracts/
│ ├── src/ # Solidity contracts
│ │ ├── InvoiceRegistry.sol
│ │ ├── PaymentEscrow.sol
│ │ └── InvoiceNFT.sol
│ ├── scripts/
│ │ └── deploy.ts # Hardhat deploy script
│ └── deployments/ # Generated address files per network
├── lib/
│ ├── contract-abis/ # TypeScript ABI constants (@workspace/contract-abis)
│ ├── db/ # Drizzle schema + migrations
│ └── api-spec/ # OpenAPI spec + codegen
└── DEPLOYMENT.md # Full deploy guide
- Node.js 24+
- pnpm 9+
- PostgreSQL (or use the Replit-managed database)
- MetaMask browser extension
# Clone and install
git clone <repo-url>
cd arcpay
pnpm install
# Set environment variables (copy and fill in)
cp artifacts/arcpay/.env.example artifacts/arcpay/.env.local
# Required: DATABASE_URL, VITE_CHAIN_ID, contract addresses
# Push database schema
pnpm --filter @workspace/db run push
# Start the API server (port 8080)
pnpm --filter @workspace/api-server run dev
# Start the frontend (port from $PORT)
pnpm --filter @workspace/arcpay run devcd contracts
pnpm install
# Compile
pnpm run compile
# Start a local Hardhat node
pnpm run node
# Deploy to local node (new terminal)
pnpm run deploy:local
# Copy addresses to frontend
cp deployments/localhost.env ../artifacts/arcpay/.env.localcd contracts
# Set your deployer key in contracts/.env
echo "DEPLOYER_PRIVATE_KEY=0x..." > .env
pnpm run deploy:arc
cp deployments/arc.env ../artifacts/arcpay/.env.localSee DEPLOYMENT.md for the complete guide including mainnet deployment, contract verification on ArcScan, and fee management.
Frontend (artifacts/arcpay/.env.local):
VITE_CHAIN_ID=5042002
VITE_INVOICE_REGISTRY_ADDRESS=0x...
VITE_PAYMENT_ESCROW_ADDRESS=0x...
VITE_INVOICE_NFT_ADDRESS=0x...
VITE_USDC_ADDRESS=0x...
VITE_EURC_ADDRESS=0x...API server:
DATABASE_URL=postgresql://...
SESSION_SECRET=...Contracts (contracts/.env):
DEPLOYER_PRIVATE_KEY=0x...
# For mainnet only:
ARC_MAINNET_USDC_ADDRESS=0x...
ARC_MAINNET_EURC_ADDRESS=0x...- Owner — the wallet that ran the deploy script. Can call
setFee()andtransferOwnership()onPaymentEscrow, andsetAuthorizedEscrow()onInvoiceRegistry. - Protocol fee — 30 bps (0.30%) of each payment, sent to
feeToonreleaseEscrow. Defaults to the deployer wallet; update withsetFee(newTreasury, 30)to point to a multisig. - Fee cap — hard-coded at 500 bps (5%) in the contract; cannot be exceeded regardless of what the owner sets.
- Payer visits
/pay/:invoiceId— fetches invoice metadata from API - Connect wallet — MetaMask prompt, switches to Arc Network automatically
- Approve —
USDC.approve(PaymentEscrow, amount)— one MetaMask tx - Fund escrow —
PaymentEscrow.fundEscrow(invoiceId)— pulls USDC into contract - Release —
PaymentEscrow.releaseEscrow(escrowId)— sends net to payee, fee to treasury, mints NFT receipt, marks invoice paid
Steps 4 and 5 can be merged into instant settlement by setting releaseDelay = 0 (current default).
| Resource | URL |
|---|---|
| Live app | https://arc-network-research.replit.app |
| Arc Testnet Explorer | https://testnet.arcscan.app |
| Arc Mainnet Explorer | https://arcscan.app |
| Arc Documentation | https://docs.arc.network |
| Circle USDC Faucet | https://faucet.circle.com |
| Circle CCTP Docs | https://developers.circle.com/stablecoins/docs/cctp-getting-started |
MIT