██╗███╗ ██╗██╗ ██╗ ██████╗ ███████╗██╗
██║████╗ ██║██║ ██║██╔═══██╗██╔════╝██║
██║██╔██╗ ██║██║ ██║██║ ██║█████╗ ██║
██║██║╚██╗██║╚██╗ ██╔╝██║ ██║██╔══╝ ██║
██║██║ ╚████║ ╚████╔╝ ╚██████╔╝██║ ██║
╚═╝╚═╝ ╚═══╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝
InvoFi is an open-source, decentralised invoice financing protocol built on Stellar Soroban. It solves a real problem: small and medium businesses often wait 30–90 days to get paid on invoices, starving them of working capital.
InvoFi lets businesses tokenise their invoices as on-chain assets and instantly receive financing from a global pool of investors. Investors earn yield. Businesses get liquidity. Everything is governed by smart contracts — no banks, no middlemen, no trust required.
Business registers invoice → Lenders compete with offers → Business accepts best offer
→ Funds available immediately → Business repays (full or partial) → Lender earns yield
InvoFi's code lives across three repos with distinct jobs:
| Repo | Role |
|---|---|
| invofi (this repo) | Integration monorepo. Vercel deploys the frontend straight out of invofi/apps/frontend. Full history, issue backlog, and roadmap live here. |
| invofi-frontend | Where frontend contributions happen — UI, hooks, client-side work. Scoped CI, its own issue queue. |
| invofi-contracts | Where contract contributions happen — Soroban/Rust. Scoped CI, its own issue queue. |
Merged PRs in the component repos are periodically pulled into this monorepo (via scripts/sync-subtrees.sh) and deployed from here.
Frontend: invofi-five.vercel.app Contract on Stellar Testnet (v0.3):
CDS3WO5K6GUBGCLMRUR4IQYLDT5SNDOMUMZFDXOMXYP6EELLTM5MMALHDeploy your own via the Deploy Contract workflow and set
NEXT_PUBLIC_CONTRACT_IDin Vercel. Without a contract configured the app runs in alpha mode (off-chain only).
git clone https://github.com/Stellar-VaultLink/invofi.git
cd invofi/apps/frontend
cp .env.local.example .env.local # fill in Supabase + contract values
npm install && npm run dev
# → http://localhost:3000- Register invoices on-chain in under 60 seconds
- Receive competing financing offers from a global investor pool
- Accept the best offer and get immediate liquidity
- Full and partial repayment tracked on-chain
- No bank account or credit history needed — your invoice is the collateral
- Browse a marketplace of verified on-chain invoices
- Submit financing offers with custom interest rates and duration
- Track active investments and yields in a live portfolio
- Transparent partial repayment history on the Stellar blockchain
- Trustless — all terms, state transitions, and repayments enforced by Soroban smart contracts
- Transparent — every action is a public transaction on Stellar, auditable by anyone
- Permissionless — anyone with a Stellar wallet can participate
- Dual auth — email/password (Supabase) and Stellar wallet (Freighter or Lobstr)
- Multi-currency — invoices denominated in XLM or USDC
- Partial repayment — businesses can repay incrementally; offer stays Financed until fully cleared
- Free to deploy — Vercel (free) + Supabase (free) + Stellar testnet
┌─────────────────────────────────────────────────────────────────┐
│ Browser (User) │
│ Next.js 14 App Router — deployed on Vercel │
│ │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ Email / Password │ │ Freighter / Lobstr Wallet │ │
│ │ auth via Supabase│ │ signs Soroban transactions │ │
│ └────────┬──────────┘ └──────────────┬───────────────┘ │
└────────────┼────────────────────────────────┼───────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌────────────────────────────┐
│ Supabase │ │ Stellar Soroban Contract │
│ Auth + mirror │ │ invofi-invoice-registry │
│ │ │ │
│ user_profiles │ │ register_invoice() │
│ invoices │ │ create_offer() │
│ financing_offers│ │ accept_offer() │
└─────────────────┘ │ repay_invoice() ◄─ partial│
│ mark_overdue() │
│ reclaim_invoice() │
│ get_invoices_by_status() │
└──────────────┬─────────────┘
│
┌──────────────▼─────────────┐
│ Stellar Horizon API │
│ Balance + tx history │
└────────────────────────────┘
No backend server. No database to manage. 100% free hosting.
invofi/
├── invofi/apps/
│ ├── contracts/ Soroban Rust smart contract
│ │ ├── lib.rs Core protocol — all 17 contract functions
│ │ ├── test.rs 21+ contract tests
│ │ ├── Cargo.toml
│ │ └── scripts/
│ │ ├── deploy.sh Build + deploy to testnet/mainnet
│ │ └── fund-and-deploy.sh Generate + fund deployer key, then deploy
│ └── frontend/ Next.js 14 web application
│ └── src/
│ ├── app/
│ │ ├── page.tsx Landing page
│ │ ├── layout.tsx Root layout + providers
│ │ ├── dashboard/ Business invoice dashboard
│ │ ├── invoices/ Create and view invoices
│ │ ├── marketplace/ Lender invoice browser
│ │ ├── portfolio/ Lender investment tracker
│ │ ├── profile/ User profile + display name
│ │ └── settings/ Account settings
│ ├── components/
│ │ ├── auth/ AuthGuard, WalletButton, WalletProvider,
│ │ │ WalletSelectDialog (Freighter + Lobstr picker)
│ │ ├── common/ ConfirmDialog, StatsCard, StatsGrid,
│ │ │ StatusBadge, PageHeader, EmptyState
│ │ ├── invoices/ InvoiceCard, InvoiceForm, InvoiceTable,
│ │ │ OfferList
│ │ ├── layout/ Navbar (dark mode + a11y), Footer
│ │ └── ui/ shadcn/ui — button, dialog, table,
│ │ badge, card, input, tabs, toast...
│ ├── hooks/ useInvoices, useOffers, useMarketplace,
│ │ useLocalStorage, useDebounce, useMediaQuery
│ └── lib/
│ ├── contract.ts Soroban contract call helpers
│ ├── freighter.ts Freighter v6 wallet helpers
│ ├── walletkit.ts @creit.tech/stellar-wallets-kit init + Lobstr
│ ├── horizon.ts Stellar Horizon API helpers
│ ├── supabase.ts Auth + database helpers
│ ├── formatters.ts Amount, date, address formatters
│ ├── csv.ts CSV export helpers
│ └── constants.ts Network config, risk tiers, enums
├── scripts/
│ ├── sync-subtrees.sh Pull/push invofi-frontend + invofi-contracts
│ └── close-issues.sh Bulk GitHub issue close
├── docs/
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
├── SECURITY.md
└── LICENSE
Contract: invofi-invoice-registry · invofi/apps/contracts/lib.rs
| Field | Type | Description |
|---|---|---|
id |
Symbol |
Unique invoice identifier |
originator |
Address |
Stellar address of the business |
amount |
i128 |
Invoice amount in stroops (10,000,000 stroops = 1 unit) |
currency |
Symbol |
XLM or USDC |
due_date |
u64 |
Unix timestamp of payment due date |
status |
InvoiceStatus |
Pending → Financed → Repaid / Overdue / Cancelled |
| Field | Type | Description |
|---|---|---|
id |
Symbol |
Unique offer identifier |
invoice_id |
Symbol |
Invoice this offer targets |
lender |
Address |
Stellar address of the investor |
amount |
i128 |
Offer amount in stroops |
currency |
Symbol |
XLM or USDC |
interest_rate |
u32 |
Basis points (500 = 5.00%) |
duration |
u64 |
Financing duration in seconds |
amount_repaid |
i128 |
Running total of stroops repaid so far |
status |
OfferStatus |
Pending → Accepted → Financed → Repaid / Rejected / Defaulted |
funded_at |
u64 |
Unix timestamp when offer was accepted |
| Function | Auth | Description |
|---|---|---|
initialize(admin, token) |
Anyone (once) | One-time setup — sets admin and SEP-41 token |
register_invoice(id, originator, amount, currency, due_date) |
Originator | Register a new invoice; validates amount > 0 and due_date > now |
get_invoice(id) |
Anyone | Read invoice state |
update_invoice_status(id, status) |
Originator | Manually change invoice status |
create_offer(offer_id, invoice_id, lender, amount, currency, rate, duration) |
Lender | Submit a financing offer; validates amount > 0, rate > 0 |
get_offer(id) |
Anyone | Read offer state |
accept_offer(offer_id, originator) |
Business | Accept offer → pulls lender's principal via prior token.approve, pays business; invoice → Financed |
reject_offer(offer_id, originator) |
Business | Reject a pending offer |
repay_invoice(invoice_id, offer_id, repayer, amount) |
Business | Pay amount stroops toward the outstanding balance. Offer stays Financed until amount_repaid >= principal + yield; then → Repaid |
mark_overdue(invoice_id) |
Anyone | Mark a past-due Financed invoice as Overdue |
reclaim_invoice(invoice_id, offer_id, lender) |
Lender | After 7-day grace period on Overdue invoice, marks offer Defaulted |
get_invoices_by_status(status) |
Anyone | Return all invoices matching a given InvoiceStatus |
set_rate(admin, tier, rate_bps) |
Admin | Set yield rate for risk tier A/B/C |
get_rate(tier) |
Anyone | Read the configured rate for a risk tier |
transfer_admin(admin, new_admin) |
Admin | Rotate the admin address |
get_admin() |
Anyone | Read the admin address |
get_token() |
Anyone | Read the configured SEP-41 token address |
raise_dispute(invoice_id, originator) |
Originator | Mark a Financed invoice as Disputed |
resolve_dispute(admin, invoice_id, target_status) |
Admin | Resolve a Disputed invoice to a new status |
get_lender_stats(lender) |
Anyone | Return aggregated stats for a lender address |
get_invoices_count() |
Anyone | Total number of registered invoices |
get_offers_count() |
Anyone | Total number of financing offers |
get_offers_by_status(status) |
Anyone | Return all offers matching a given status |
get_invoices_by_currency(currency) |
Anyone | Return invoices denominated in a specific asset |
get_invoices_due_before(timestamp) |
Anyone | Return invoices due before a Unix timestamp |
get_invoices_paginated(offset, limit) |
Anyone | Page through all invoices |
get_offers_paginated(offset, limit) |
Anyone | Page through all offers |
batch_get_invoices(ids) |
Anyone | Fetch multiple invoices by ID in one call |
version() |
Anyone | Return the contract semver string |
Every state-mutating function publishes a Soroban contract event, so indexers
and UIs can track protocol activity without polling. Topics are
(event_name, subject_id) — filter by invoice/offer id without decoding payloads.
| Event | Emitted by | Data |
|---|---|---|
inv_reg |
register_invoice |
(originator, amount, due_date) |
off_new |
create_offer |
(invoice_id, lender, amount, interest_rate) |
off_acc |
accept_offer |
(invoice_id, lender, amount) |
off_rej |
reject_offer |
invoice_id |
off_wdr |
withdraw_offer |
lender |
off_def |
reclaim_invoice |
(invoice_id, lender) |
inv_rep |
repay_invoice |
(offer_id, amount, fully_repaid) |
inv_ovd |
mark_overdue |
due_date |
inv_cxl |
cancel_invoice |
originator |
inv_dsp |
raise_dispute |
originator |
inv_rsl |
resolve_dispute |
new_status |
register_invoice()
│
▼
[Pending] ────────────────────────────────────── reject_offer() ──► stays Pending
│
accept_offer() ── pays principal to business
│
▼
[Financed] ◄──────────────────────────────────── repay_invoice() (partial)
│
├── repay_invoice() (balance cleared) ──────► [Repaid]
│
├── mark_overdue() ────────────────────────► [Overdue]
│ │
│ reclaim_invoice()
│ (after 7-day grace)
│ │
│ ▼
│ offer → [Defaulted]
│
└── raise_dispute() (originator) ──────────► [Disputed]
│
resolve_dispute() (admin)
│
├──► [Financed] (dispute withdrawn)
└──► [Cancelled] (dispute upheld)
| Layer | Technology | Why |
|---|---|---|
| Smart Contracts | Rust + Soroban SDK 22 | Native Stellar contract platform |
| Frontend | Next.js 14 (App Router) + TypeScript 5.5 | Free Vercel deployment, SSR |
| Styling | Tailwind CSS + shadcn/ui | Fast, accessible, composable |
| Auth | Supabase | Free tier, row-level security |
| Wallet | Freighter v6 + Lobstr via @creit.tech/stellar-wallets-kit |
Multi-wallet — users choose at connect time |
| Data Fetching | TanStack Query v5 | Caching, background refetch |
| Forms | React Hook Form + Zod | Type-safe validation |
| Icons | Lucide React | Consistent icon set |
| Stellar SDK | @stellar/stellar-sdk v16 |
Contract calls, Horizon queries |
- Node.js 20+
- Rust 1.70+ with
wasm32-unknown-unknowntarget (rustup target add wasm32-unknown-unknown) - A Stellar wallet: Freighter (browser extension) or LOBSTR (mobile / browser extension)
- A free Supabase account
git clone https://github.com/Stellar-VaultLink/invofi.git
cd invofi- Create a project at supabase.com.
- Run the schema below in SQL Editor.
- Copy your Project URL and Anon Key from Settings → API.
cd invofi/apps/frontend
cp .env.local.example .env.local
# Fill in NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_CONTRACT_IDnpm install && npm run dev
# → http://localhost:3000cd ../contracts
cargo test # 21+ tests
stellar contract buildcargo install --locked stellar-cli
stellar keys generate --global invofi-deployer --network testnet
stellar keys fund invofi-deployer --network testnet
stellar contract build
stellar contract deploy \
--wasm target/wasm32v1-none/release/invofi_invoice_registry.wasm \
--source invofi-deployer \
--network testnet
# Copy CONTRACT_ID into .env.local, then call initialize() once
stellar contract invoke \
--id <CONTRACT_ID> --source invofi-deployer --network testnet \
-- initialize --admin <ADMIN_ADDRESS> --token <SEP41_TOKEN>After deploying, copy the printed contract ID into NEXT_PUBLIC_CONTRACT_ID in your .env.local or Vercel dashboard. You can also run the Deploy Contract GitHub Actions workflow (.github/workflows/deploy-contract.yml) for a one-click Testnet deploy.
create table user_profiles (
id uuid primary key references auth.users(id),
email text not null,
role text not null check (role in ('business', 'lender')),
display_name text,
wallet_address text,
created_at timestamptz default now()
);
create table invoices (
id text primary key,
originator text not null,
originator_id uuid references auth.users(id),
amount text not null,
currency text not null,
due_date timestamptz not null,
status text not null default 'Pending',
created_at timestamptz default now()
);
create table financing_offers (
id text primary key,
invoice_id text references invoices(id),
lender_id uuid references auth.users(id),
lender text not null,
amount text not null,
currency text not null,
interest_rate integer not null,
duration integer not null,
amount_repaid text not null default '0',
status text not null default 'Pending',
funded_at integer default 0,
created_at timestamptz default now()
);
alter table user_profiles enable row level security;
alter table invoices enable row level security;
alter table financing_offers enable row level security;
create policy "Anyone can read invoices" on invoices for select using (true);
create policy "Owner can insert invoices" on invoices for insert with check (originator_id = auth.uid());
create policy "Owner can update invoices" on invoices for update using (originator_id = auth.uid());
create policy "Anyone can read offers" on financing_offers for select using (true);
create policy "Lender can insert offers" on financing_offers for insert with check (lender_id = auth.uid());
create policy "Parties can update offers" on financing_offers for update
using (lender_id = auth.uid() or
exists (select 1 from invoices where id = invoice_id and originator_id = auth.uid()));
create policy "Own profile" on user_profiles for all using (id = auth.uid());- Push fork to GitHub.
- Vercel → New Project → import → set Root Directory to
invofi/apps/frontend. - Add environment variables:
| Variable | Value |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Your Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Your Supabase anon key |
NEXT_PUBLIC_CONTRACT_ID |
Output from stellar contract deploy |
NEXT_PUBLIC_STELLAR_NETWORK |
testnet |
NEXT_PUBLIC_RPC_URL |
https://soroban-testnet.stellar.org |
NEXT_PUBLIC_HORIZON_URL |
https://horizon-testnet.stellar.org |
- Core invoice registry contract (register, offer, accept, reject, repay, overdue, reclaim)
- Partial repayment —
amount_repaidtracking, offer stays Financed until balance cleared - Query helpers —
get_invoices_by_status,get_invoices_by_currency,get_invoices_due_before, pagination, batch queries - Dispute lifecycle —
raise_dispute/resolve_disputewith admin resolution - Lender stats —
get_lender_statstracking total offered, accepted, pending, repaid - Input validation —
amount >= MIN_INVOICE_AMOUNT,due_date > now,interest_rate > 0,duration <= MAX_OFFER_DURATION_SECS - Next.js 14 frontend with multi-wallet support (Freighter + LOBSTR via
@creit.tech/stellar-wallets-kit) - Alpha / demo mode — app runs fully off-chain when no contract is deployed; shows info banner
- Supabase auth (email + wallet), dark mode, accessibility, SEO metadata
- Marketplace and portfolio views, sortable InvoiceTable, StatsCard KPIs
- Profile page, ConfirmDialog, EmptyState, LoadingSkeleton components
- One-click Testnet deploy via GitHub Actions workflow
- Protocol events (v0.3) — every state transition published on-chain for indexers and activity feeds
- Marketplace sorting (newest, amount, due date) and Stellar Expert explorer links
- Mainnet deployment
- Oracle-based invoice verification and risk scoring
- Multi-signature treasury and escrow
- KYC / AML with SEP-12 support
- Contract upgradeability with timelock governance
Read CONTRIBUTING.md before opening a pull request. For bugs and features, use GitHub Issues.
Do not open a public issue for security vulnerabilities. See SECURITY.md for responsible disclosure.
MIT © 2026 InvoFi Contributors. See LICENSE.