The Problem

Freelancers and small businesses are sitting on billions of dollars of locked capital — invoices that are legitimate, enforceable, and already earned, but won't pay out for 30, 60, or 90 days. Traditional invoice factoring charges 2–5% fees, requires revealing sensitive business data to third parties, and still takes days to process. The money is yours. You just can't use it yet.

What vopo Does

vopo turns that waiting period into a yield-generating opportunity — automatically.

  1. Mint — Connect your wallet, enter your invoice details, and mint an invoice NFT on Mantle Sepolia. Your invoice data never touches the chain in plain text: we use keccak256 commitments so only you can prove what you hold.

  2. Deposit — Drop the NFT into the YieldVault. Choose a strategy: Conservative (3.5% APY) or Aggressive (7% APY). Your invoice is now earning while you wait.

  3. Watch the Agent Work — A TypeScript AI agent runs 24/7 and streams its reasoning to the frontend in real time. It reads invoice risk scores from Pyth Network, evaluates days-until-due, payment probability, and current yield rates, then decides whether to change your strategy.

  4. On-chain Execution — When the agent is confident enough (≥70%), it writes its decision through AgentRouter on-chain. The decision, reasoning, and confidence score are all recorded and visible in the dashboard.

  5. Withdraw — When your invoice is paid or you want out, withdraw the NFT and claim accrued yield.

What Makes It Different

Most "AI + DeFi" demos call an API and show a chatbot. vopo's agent executes on-chain. Every strategy change it makes is a real transaction, signed by an authorized wallet, recorded permanently on Mantle Sepolia, and visible on the explorer. The "Living Agent" feed on the dashboard streams the agent's reasoning in real time — you can watch it think, decide, and act.

The privacy layer is also real. Invoice details are stored only in the user's browser as a salt. On-chain, only the keccak256 commitment exists. An authorized counterparty can verify an invoice is genuine without ever learning the amount or client name.

How We Built It

Smart Contracts (Solidity + Hardhat, Mantle Sepolia)

  • InvoiceNFT — ERC-721 with privacy commitments and oracle-driven risk scores
  • YieldVault — deposit/withdraw/strategy management with simulated Aave-style yield accrual
  • AgentRouter — records agent decisions with confidence scores and executes them against the vault
  • PrivacyRegistry — Merkle-based selective disclosure
  • PythOracle + AaveV3YieldSource — integrated oracle and yield source

Agent (TypeScript/Node.js)

  • Runs a continuous loop: fetch deposits → analyze each invoice → generate LLM explanation → optionally execute → broadcast via WebSocket
  • Uses Qwen for natural language reasoning summaries
  • Pyth Network for real-time invoice risk pricing
  • Each RPC call fans out across multiple Mantle Sepolia endpoints to avoid rate limiting

Frontend (Next.js 15 + React 19 + wagmi/viem)

  • Dashboard with live TVL, yield, active invoices, and agent activity feed
  • Mint page with privacy-preserving commitment generation
  • Portfolio, issuer, and settings views
  • QuickBooks OAuth integration for importing real invoice data
  • Explorer link on every transaction

Infrastructure

  • Docker containers for both app and agent, deployed to Ubuntu via GitHub Actions CI/CD
  • NEXT_PUBLIC_* vars baked at build time with placeholder strings replaced by entrypoint at runtime

Challenges We Faced

The NEXT_PUBLIC_* baking problem. Next.js embeds these vars into the JS bundle at build time, not runtime — in both the client chunks and the server route bundles. We had to extend the container entrypoint to sed-replace placeholder strings in both directories, and add guards in API routes to detect unreplaced placeholders before constructing URLs.

EIP-1559 gas on production HTTPS. Locally, MetaMask is lenient with legacy type-0 transactions. On an HTTPS origin it enforces EIP-1559 fee validation strictly, causing transactions with a gasPrice override (from simulateContract) to get stuck in the mempool indefinitely. The fix was to remove simulateContract entirely and let the wallet use native EIP-1559 fee estimation via writeContractAsync.

Receipt polling across multiple RPCs. viem's fallback() transport only switches providers on network errors — a null receipt from a 200 response doesn't trigger a retry. We poll each RPC URL independently in the force-settle path to handle nodes that haven't yet indexed a transaction another node already confirmed.

QuickBooks OAuth in a Docker container. The NEXT_PUBLIC_APP_URL placeholder surviving in server-side route bundles caused new URL('__VOPO_APP_URL__/...') to crash. Combined with Docker's --env-file parser including trailing whitespace as part of values (breaking clientId), this took several debug cycles to fully resolve.

What We Learned

Real deployment is where the interesting bugs live. The gap between "works on localhost" and "works on production HTTPS in a Docker container behind a reverse proxy" exposed every assumption we'd made about environment variables, gas estimation, and transaction indexing. Mantle Sepolia's low fees and fast finality made on-chain iteration fast enough that we could actually fix these in real time without burning through test funds.

What's Next

  • Integrate Lendle protocol for more yield
  • Add Noir ZK circuits for amount range proofs (prove an invoice is > $X without revealing the amount)
  • Secondary market for invoice NFTs

Built With

Share this project:

Updates