Get paid when you ship, not when your buyer pays.
EarnX turns a verified export invoice into cash for an African exporter today. Investors anywhere fund it in Paxos USDG (or Circle USDC) and earn the yield when the buyer pays. Everything that moves money is enforced by smart contracts on Robinhood Chain and Arbitrum.
Live app → earnx-app.vercel.app · Contracts · Robinhood Chain protocol · Arbitrum Sepolia protocol
| Live app | earnx-app.vercel.app (Robinhood Chain testnet by default; switch to Arbitrum in the header) |
| One invoice, every step on-chain | Invoice #19 on Robinhood Chain: submitted by a passkey account → verified, priced by the Stylus engine, record minted → buyer confirmed (signed statement on IPFS) → funded, exporter paid in the same transaction → repaid by the buyer → claimed by the investor |
| Contracts | Deployments, source-verified on Blockscout · Solidity · Rust risk engine |
| Tests | 29 Foundry tests including fuzzing, 6 Rust tests, run in CI on every push |
| Sponsor technology | Where each one is used |
| Demo video | On the HackQuest project page; its script and every transaction it shows are in docs/video |
A confirmed export order is not cash. An exporter pays farmers, processors and freight up front, then waits weeks or months for the buyer to pay. Banks rarely lend against those invoices, so good orders are turned down, or sold to middlemen at a discount.
- $74–92 billion of trade finance requested by African businesses went unmet in 2024 (African Development Bank).
- 37% of trade finance applications from African firms were rejected between 2020 and 2024 (same source).
- Africa's factoring market was about €50 billion in 2024; Afreximbank estimates it must reach €240 billion to close the SME gap (Afreximbank via GTR).
My mother, Mama Dora, is a farmer in Delta State, Nigeria. She grows cassava and other crops, and for as long as I can remember her harvest has paid for everything in our home.
Then my father had a stroke. The hospital needed money that week. Like most Nigerian families, we had no health insurance: of every ₦1,000 spent on healthcare in Nigeria, about ₦720 comes straight out of a family's own pocket (World Bank).
It happened just after harvest, when a farmer has the most to show and the least cash in hand. What money she had went to the farmhands who had helped bring the crop in, and it still wasn't enough to pay them all. The harvest was worth far more than the hospital bill, but it wasn't money yet.
So we did what farming families across Africa do. We sold part of the crop cheaply, just to get cash quickly, and we went to the bank. Between the fees and the interest rate, the loan cost far more than we could carry, and it bankrupted us.
I built EarnX so that the value of work already done reaches people when they need it, at a fair price. We start with African exporters, whose invoices we can verify on-chain today. Every invoice we fund is a family that doesn't have to choose between the harvest and the hospital.
— Godswill Idolor, founder, and son of a Delta State farmer
For exporters
- Sign in with a fingerprint or Face ID (a passkey smart account). No wallet app, no seed phrase, and network fees are sponsored.
- Upload the invoice and shipping documents. They go to IPFS; their fingerprint goes on-chain.
- Once verified and fully funded, up to 90% of the invoice lands in your account in the same transaction.
- Every verified invoice mints a non-transferable record to you: an on-chain trade history that belongs to you, not to a bank's filing cabinet.
For investors
- Browse real invoices with their terms, risk score and documents, without connecting a wallet.
- Fund any amount from 1 USDG. When the buyer pays, claim your share of principal plus yield.
- Check for yourself that an invoice's documents still match what the verifier reviewed: the app hashes them in your browser and compares with the chain.
sequenceDiagram
autonumber
actor E as Exporter (passkey)
participant S as Pre-screen (/api/verify)
participant C as Buyer confirmation (/api/confirm)
participant P as EarnXProtocol
participant R as Risk engine (Stylus)
actor B as Buyer
actor I as Investors
E->>P: submitInvoice(amount, due date, buyer, docs CID + hash)
E->>S: run the pre-screen
S->>S: documents match the hash · quantity × price = total<br/>World Bank price · no duplicate on either chain · exporter's limit
opt invoice above $1,000
B->>C: sign the statement built from the on-chain facts
C-->>S: signed statement on IPFS
end
S->>P: verifyInvoice(risk score), or rejectInvoice(reason)
P->>R: quote(risk score, tenor, amount)
R-->>P: APR + advance
P-->>E: soulbound record NFT minted
I->>P: invest(USDG)
P-->>E: advance paid in the transaction that completes funding (1% to the reserve)
B->>P: repay(amount), in parts or in full
I->>P: claim() pro-rata principal + yield
Note over P: Past due + grace period: anyone can markDefault.<br/>The first-loss reserve covers investor principal, and recoveries refill it.
Invoice lifecycle, enforced by EarnXProtocol:
stateDiagram-v2
[*] --> Submitted: submitInvoice
Submitted --> Funding: verifyInvoice / verifyInvoiceWithSig
Submitted --> Rejected: rejectInvoice
Submitted --> Cancelled: cancelInvoice
Funding --> Funded: invest reaches the target, exporter paid
Funding --> Cancelled: cancelInvoice (refunds investors)
Funded --> Repaid: repay in full
Funded --> Defaulted: markDefault after due date + grace
Defaulted --> Defaulted: repay (recoveries)
Pricing is computed on-chain by the Rust engine from a published formula, so nobody, including EarnX, can override it:
- APR = 8% + 0.15% per risk point + 0.01% per 3 days of term beyond 30 + 0.25% above $50k or 0.5% above $100k, kept between 5% and 50%.
- Advance = 90% − 0.25% per risk point above 30 − 5% for terms over 120 days, kept between 70% and 90%.
- Risk scores above 80 cannot be funded.
Who stands behind an invoice
| Exporter not verified | Business verified | |
|---|---|---|
| Approved automatically up to | $1,000 per invoice | $250,000 per invoice |
| Buyer confirmation | Optional, shown to investors | Required above $1,000 |
| Effect on the risk score | −5, and −5 more once the buyer confirms |
What keeps it honest
- Short and self-liquidating. Each advance is tied to one shipment and repaid when that buyer pays. Terms are capped at 365 days on-chain.
- First-loss reserve. 1% of every advance, plus any capital partners add, covers investor principal before investors lose anything. It can only be used for that.
- Documents you can check. Each invoice stores the keccak256 of a manifest listing every document and its own hash. A verifier signature is bound to that exact hash.
- Know who stands behind it. Exporters who haven't verified their business are approved automatically only up to $1,000 per invoice. Verification is an on-chain registry (
VERIFIED_EXPORTER_ROLEon the protocol), granted after a reviewer checks the business against its national registry; the request is encrypted before it is stored. - The buyer confirms the debt. Above $1,000 the buyer must sign a statement built from the invoice's on-chain facts: they ordered the goods, the invoice is genuine, and they will pay the EarnX contract. Wallet and passkey signatures (ERC-1271 / ERC-6492) are checked and the signed statement is pinned to IPFS for anyone to re-check. A confirmed buyer or a verified business also lowers the risk score, and so the price.
- No double financing. The pre-screen rejects an invoice that reuses any document, the same bundle, or the same invoice number for the same buyer on either chain.
- Over-invoicing is caught. Unit prices are compared with World Bank commodity benchmarks; far above market is rejected.
- No hidden levers. The admin can pause new activity and allow-list stablecoins. It cannot move investor funds.
- Open earnx-app.vercel.app and pick a network (Robinhood Chain or Arbitrum).
- Browse Invest. Invoices marked sample are fictional and seeded for the demo; invoice #6 on each chain has already gone through the whole cycle to Repaid.
- To fund one, get testnet ETH (Robinhood, Arbitrum) and test USDG or USDC, connect a wallet and invest from 1 USDG.
- To see the exporter side, go to Get paid early, sign in with a passkey or wallet, upload a PDF and submit. The automated pre-screen checks the documents and opens the invoice for funding within seconds.
| Network | EarnXProtocol | EarnXInvoiceNFT | Settlement |
|---|---|---|---|
| Robinhood Chain testnet (46630) | 0xA7fC55ca10c05aA2a0e0Cef5e00f15B08Caf4a99 |
0x7c2e27323578C67B4c2E847024D80091586503d6 |
Paxos USDG |
| Arbitrum Sepolia (421614) | 0x0D0C0eE2a93D4E6d912da43810Ca8f327BDc7341 |
0xc9A10EDA07ea8D90dB95254540efb7F00907f888 |
Paxos USDG, Circle USDC |
Rust risk engine (Stylus): Robinhood Chain 0x454aeA0eDA332a09FFc61C5799B336AEa24Cd863 · Arbitrum Sepolia 0xb78d4d4FDCBd5e2E73405091138B08bd1707d551
The Solidity contracts are source-verified on Blockscout.
flowchart LR
subgraph web["Web app · React + Vite"]
UI["Exporter · buyer · investor pages"]
end
subgraph acct["Accounts"]
PK["Passkey smart account<br/>ZeroDev Kernel v3.1 · ERC-4337"]
WL["Browser wallet<br/>wagmi + RainbowKit"]
end
subgraph api["Serverless functions · Vercel"]
UP["/api/upload"]
VF["/api/verify · pre-screen"]
CF["/api/confirm · buyer statements"]
KY["/api/kyb · encrypted requests"]
RP["/api/rpc · read-only proxy"]
end
subgraph chain["Robinhood Chain testnet · Arbitrum Sepolia"]
P["EarnXProtocol<br/>Solidity · OpenZeppelin 5.7"]
RE["Risk engine<br/>Rust on Arbitrum Stylus"]
NFT["EarnXInvoiceNFT<br/>ERC-5192 soulbound"]
TK[("Paxos USDG · Circle USDC")]
end
IP[("IPFS via Pinata")]
AL[("Alchemy")]
WB[("World Bank Pink Sheet")]
UI --> PK & WL
PK -- "sponsored user operations" --> P
WL -- "transactions" --> P
UI --> UP & VF & CF & KY & RP
UP --> IP
CF --> IP
KY -- "AES-256-GCM" --> IP
VF -- "VERIFIER_ROLE key" --> P
VF -. "documents" .-> IP
VF -. "benchmarks" .-> WB
RP --> AL --> P
P -- "quote()" --> RE
P -- "mint, status" --> NFT
P <-->|"stablecoins in and out"| TK
| Part | What it is |
|---|---|
contracts/ |
Foundry project. EarnXProtocol (lifecycle, pricing, reserve) and EarnXInvoiceNFT (ERC-5192 soulbound records with on-chain SVG metadata). OpenZeppelin 5.7: AccessControl, SafeERC20, ReentrancyGuard, Pausable, EIP-712, Nonces. 29 tests including fuzz tests; CI on every push. |
contracts/stylus/risk-engine |
Rust risk engine on Arbitrum Stylus. Prices every invoice on-chain from a published formula: APR = 8% + 0.15% per risk point + term and size premiums; advance = 90%, reduced for higher risk and long terms. No storage, no owner; 4.3 KB of WASM with Rust unit tests. |
app/ |
Vite + React + TypeScript, wagmi + RainbowKit for wallets, ZeroDev Kernel v3.1 for passkey accounts with sponsored gas, Tailwind. ABIs and addresses are generated from contracts/ so the app cannot drift from the chain. |
app/api/upload |
Serverless function that pins documents to IPFS through Pinata and returns the manifest CID and hash. The Pinata key never reaches the browser. |
app/api/verify |
Automated pre-screen for the testnet: re-fetches the documents from IPFS, checks them against the on-chain hash, the market price and every invoice on both chains for duplicates, applies the business-verification limit and buyer-confirmation rule, then verifies or rejects the invoice with a key that holds VERIFIER_ROLE and nothing else. |
app/api/confirm |
Buyer confirmation: serves the statement for an invoice, checks the buyer's signature (EOA, ERC-1271 or ERC-6492) and pins the signed statement to IPFS, indexed by invoice. |
app/api/kyb |
Business verification requests, encrypted with AES-256-GCM before they are pinned. app/scripts/kyb-requests.mjs decrypts them for the reviewer and prints the on-chain command that records the result. |
contracts/ Foundry project: EarnXProtocol, EarnXInvoiceNFT, tests, deploy and seed scripts
contracts/stylus/ Rust risk engine for Arbitrum Stylus
app/ React app
app/api/ Serverless functions: upload, verify, confirm, kyb, rpc, prices
app/scripts/ Contract sync, World Bank benchmark sync, business-verification review
docs/video/ The pitch video's script, recorder config and the transactions it shows
| Technology | How EarnX uses it | Where |
|---|---|---|
| Robinhood Chain | Main deployment; the app's default network | contracts/deployments/46630.json |
| Paxos USDG | The settlement stablecoin on both chains: investments, payouts, repayments, claims | contracts/deployments/ |
| Arbitrum Stylus | The risk engine that prices every invoice, written in Rust | contracts/stylus/risk-engine |
| ZeroDev | Passkey smart accounts and the paymaster that sponsors exporters' gas | app/src/lib/passkey.ts |
| OpenZeppelin | AccessControl, SafeERC20, ReentrancyGuard, Pausable, EIP-712, Nonces | contracts/src |
| Alchemy | RPC for the app (behind a read-only proxy) and for the serverless functions | app/api/rpc.ts, app/src/lib/serverRpc.ts |
Why Robinhood Chain and Arbitrum. Both are Arbitrum chains with low fees and fast blocks. Robinhood Chain is built for real-world assets and has USDG natively; Arbitrum has deep stablecoin liquidity and native USDC. The same contracts run on both.
Everything before the buildathon is tagged pre-buildathon (an earlier Mantle Sepolia version). See every change since → EarnX also had an earlier Arbitrum prototype, entered in Arbitrum Open House NYC from a separate codebase; none of that code is in this repository.
During the buildathon we:
- rewrote the contracts from scratch: the previous contract accepted deposits but had no payout, repayment or claim path, and approved every invoice automatically;
- added the first-loss reserve, EIP-712 verifier signatures bound to document hashes, and soulbound trade records;
- wrote a risk engine in Rust on Arbitrum Stylus that prices every new invoice on-chain, and plugged it into the protocol on both chains;
- wrote the test suite and CI, then deployed and verified on Robinhood Chain testnet and Arbitrum Sepolia with Paxos USDG;
- rebuilt the app around live on-chain data (the old one showed hardcoded figures), added passkey accounts with sponsored gas, IPFS uploads and the automated verifier;
- added the trust layer: tiered limits from an on-chain business-verification registry, signed buyer confirmations, a cross-chain duplicate check, and a World Bank market-price check;
- removed hardcoded credentials and the unused Morph/Mantle-era code.
- Testnet only. The contracts are not audited. Do not use real funds.
- Verification is the trust point. On the testnet an automated pre-screen approves small invoices from unverified exporters and larger ones only with a verified business and the buyer's signature. Business checks are manual today; before mainnet they move to a licensed identity partner, and buyer contacts are verified independently of the exporter (today a buyer is whoever holds the account that signs). The contract already accepts any approved verifier, including signed approvals from off-chain reviewers.
- Repayment depends on the buyer. The reserve softens defaults but cannot remove them. Default rates and reserve levels are shown publicly in the app.
git clone --recurse-submodules https://github.com/big14way/earnx.git && cd earnx
# contracts (needs Foundry)
cd contracts && forge test && cd ..
# app
cd app && npm install && cp .env.example .env.local # add a Reown project ID (and ZeroDev for passkeys)
npm run devDeployment scripts and the sample-invoice seeder are documented in contracts/README.md.
