Decentralized Invoice Factoring Platform on Algorand
Instant liquidity for businesses. Fractional investing for everyone.
🎬 [WATCH DEMO VIDEO HERE]
Innuid is a revolutionary blockchain-based invoice factoring platform built on Algorand that democratizes access to short-term business financing. It connects businesses needing immediate cash flow with a distributed network of investors seeking stable returns.
For Small Businesses:
- 🕐 30-90 day payment terms create cash flow crises
- 💸 Traditional factoring costs 3-5% in fees
- 🏦 Banks require lengthy applications and collateral
- 🚫 Limited access for small/new businesses
┌─────────────────────────────────────────────────┐
│ React Frontend (shadcn/ui) │
│ ┌──────────────────────────────────────────┐ │
│ │ Typed AppClient (Auto-generated) │ │
│ └────────────────┬─────────────────────────┘ │
└───────────────────┼─────────────────────────────┘
│ AlgoKit Utils TS
▼
┌─────────────────────────────────────────────────┐
│ Algorand Smart Contract Layer │
│ ┌──────────────────────────────────────────┐ │
│ │ Innuid Contract (Algorand TypeScript)│ │
│ │ │ │
│ │ Global State: nextInvoiceId │ │
│ │ │ │
│ │ Box Storage (BoxMaps): │ │
│ │ ├─ invoiceSellers[id] → Account │ │
│ │ ├─ invoiceBuyers[id] → Account │ │
│ │ ├─ invoiceTotalAmounts[id] → uint64 │ │
│ │ ├─ invoiceAmountsRaised[id] → uint64 │ │
│ │ ├─ invoiceStatuses[id] → uint64 │ │
│ │ └─ investments[id+addr] → uint64 │ │
│ │ │ │
│ │ Methods: │ │
│ │ ├─ createInvoice() → invoiceId │ │
│ │ ├─ approveInvoice(id) │ │
│ │ ├─ investInInvoice(payment, id) │ │
│ │ ├─ payInvoice(payment, id) │ │
│ │ └─ claimPayout(id) → Inner Txn │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
TestNet Application ID: 748002191
Explorer Link: View on Lora Explorer
The Innuid smart contract is written in Algorand TypeScript and compiled to TEAL bytecode using PuyaTS. It leverages several unique Algorand features to enable trustless, scalable invoice factoring.
1. State Management
// Global State - Single counter for invoice IDs
nextInvoiceId = GlobalState<uint64>({ initialValue: Uint64(0) })
// Box Storage - Unlimited scalability for invoices
invoiceSellers = BoxMap<uint64, Account>({ keyPrefix: 'is' })
invoiceBuyers = BoxMap<uint64, Account>({ keyPrefix: 'ib' })
invoiceTotalAmounts = BoxMap<uint64, uint64>({ keyPrefix: 'ia' })
invoiceAmountsRaised = BoxMap<uint64, uint64>({ keyPrefix: 'ir' })
invoiceMinInvestments = BoxMap<uint64, uint64>({ keyPrefix: 'im' })
invoiceDueDates = BoxMap<uint64, uint64>({ keyPrefix: 'id' })
invoiceStatuses = BoxMap<uint64, uint64>({ keyPrefix: 'st' })
// Investment Tracking - Composite key: invoiceId + investorAddress
investments = BoxMap<bytes, uint64>({ keyPrefix: 'inv' })
Why Box Storage?
- Unlimited invoices: Global state maxes out at 64 key-value pairs
- Cost-effective: ~$0.001-0.01 per invoice (vs $20-100 on Ethereum)
- Dynamic allocation: Boxes created on-demand as invoices are created
- Per-investor tracking: Each investor's stake tracked individually
2. Invoice Lifecycle States
const INVOICE_PENDING = Uint64(0) // Created, awaiting buyer approval
const INVOICE_ACTIVE = Uint64(1) // Approved, open for investment
const INVOICE_FUNDED = Uint64(2) // Fully funded, awaiting payment
const INVOICE_PAID = Uint64(3) // Paid by buyer, ready for claims
📝 createInvoice()
@abimethod()
createInvoice(
buyer: Address,
totalAmount: UintN64,
minInvestment: UintN64,
dueDate: UintN64
): UintN64
What it does:
- Generates unique invoice ID from global counter
- Creates 7 box entries for invoice data
- Sets status to
PENDING - Returns invoice ID to seller
Cost: ~0.03 ALGO in box storage MBR
✅ approveInvoice()
@abimethod()
approveInvoice(invoiceId: UintN64): void
What it does:
- Verifies caller is the designated buyer
- Confirms invoice is in
PENDINGstate - Updates status to
ACTIVE
Critical for trust: Prevents sellers from creating fake invoices
💰 investInInvoice()
@abimethod()
investInInvoice(payment: gtxn.PaymentTxn, invoiceId: UintN64): void
What it does:
- Validates atomic transaction group (payment + app call must be grouped)
- Verifies payment goes to seller
- Checks payment meets minimum investment
- Records investment in composite-key box:
concat(invoiceId, investorAddress) - Updates total amount raised
- Changes status to
FUNDEDwhen fully financed
Key security feature: Atomic transactions
- Transaction 1: Investor sends ALGO to seller
- Transaction 2: Smart contract records investment
- Both succeed or both fail - no possibility of fake investments or lost funds
💵 payInvoice()
@abimethod()
payInvoice(payment: gtxn.PaymentTxn, invoiceId: UintN64): void
What it does:
- Verifies caller is the buyer
- Confirms invoice is
FUNDED - Validates payment goes to contract address (not seller!)
- Verifies full amount is paid
- Updates status to
PAID
Design decision: Funds held in contract escrow for investor payouts
🎁 claimPayout()
@abimethod()
claimPayout(invoiceId: UintN64): void
What it does:
- Confirms invoice is
PAID - Retrieves investor's stake from box storage
- Executes inner transaction to send funds to investor
- Sets investment to 0 to prevent double-claiming
Inner Transaction Example:
itxn
.payment({
receiver: Txn.sender,
amount: invested,
fee: Uint64(0), // Caller pays transaction fee
})
.submit()
Why inner transactions are revolutionary:
- Smart contract sends payments autonomously
- No withdrawal pattern needed
- Atomic with state updates
- No intermediary required
📊 getInvoiceInfo() & getInvestment()
Read-only methods for querying contract state without transactions.
Innuid leverages Algorand-specific features that make this project uniquely possible:
Traditional Limitations:
- Ethereum: 64 storage slots per contract (~$20-100 per slot)
- Solana: Rent-based storage (ongoing payments required)
Algorand's Advantage:
- Unlimited boxes per application
- Fixed MBR cost:
2500 + 400*(keySize + valueSize)microAlgos - ~$0.001 per invoice vs $100+ on other chains
Our Implementation:
// Each invoice spans multiple BoxMaps
// Support for 10,000+ concurrent invoices
// Total storage cost: ~$10-100
The Challenge: How do you ensure payment and investment recording happen together?
Algorand's Solution:
// Transaction Group
[
Payment: Investor → Seller (60 ALGO),
AppCall: Record investment in contract
]
// Both succeed or both fail - guaranteed by protocol
What this prevents:
- ❌ Investor sends money but investment isn't recorded
- ❌ Investment recorded without actual payment
- ❌ Front-running or manipulation
Traditional Approach: Users "withdraw" funds from contract (pull pattern)
Algorand's Approach: Contract "sends" funds to users (push pattern)
// Contract autonomously sends payment
itxn.payment({
receiver: investor,
amount: return_amount
}).submit()
Benefits:
- No custody risk
- Instant settlement
- Atomic with state changes
Cost Comparison:
| Operation | Algorand | Ethereum |
|---|---|---|
| Transaction Fee | 0.001 ALGO (~$0.0001) | $5-50 |
| Create Invoice | $50-200 | |
| Invest | 0.001 ALGO | $10-100 |
| Claim Payout | 0.001 ALGO | $10-50 |
Impact:
- Micro-investments of $1 are economically viable
- No platform fees needed
- Accessible to everyone
Algorand: 2.6 second blocks with immediate finality Result: Complete invoice flow really fast
Compare to:
- Ethereum: 12-15 seconds + reorg risk (wait 6-12 blocks)
- Bitcoin: 10 minutes + wait for confirmations
- Traditional finance: 3-5 business days
Innuid/
├── projects/
│ └── Innuid-contracts/
│ ├── contracts/
│ │ └── contract.algo.ts # Main smart contract
│ ├── __test__/
│ │ ├── contract.algo.spec.ts # Unit tests
│ │ └── contract.e2e.spec.ts # E2E tests
│ ├── deploy-config.ts # Deployment script
│ └── artifacts/ # Compiled contract + clients
│
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ │ ├── ui/ # shadcn/ui components
│ │ │ ├── InvoiceCard.tsx
│ │ │ ├── InvestmentForm.tsx
│ │ │ └── PayoutClaim.tsx
│ │ ├── lib/
│ │ │ └── algorand.ts # Algorand client setup
│ │ ├── App.tsx
│ │ └── main.tsx
│ ├── tailwind.config.js
│ └── vite.config.ts
│
└── README.md
Screen-Recording.mp4
- Node.js 18+ and npm
- Python 3.10+ (for AlgoKit)
- Docker (optional, for LocalNet)
- AlgoKit CLI
1. Install AlgoKit
# macOS/Linux
brew install algorandfoundation/tap/algokit
# Windows
winget install algorandfoundation.algokit
# Or via pipx
pipx install algokit
2. Clone the Repository
git clone https://github.com/ss2d22/Innuid.git
cd Innuid
3. Install Dependencies
# Install all project dependencies
npm install
# Or use AlgoKit
algokit project bootstrap all
1. Start LocalNet (Optional)
algokit localnet start
2. Compile Smart Contract
cd projects/Innuid-contracts
npm run build
3. Run Tests
# Unit tests
npm test
# E2E tests
npm run test:e2e
4. Deploy to LocalNet
npm run deploy:localnet
5. Start Frontend
cd ../../frontend
npm run dev
Visit http://localhost:5173
1. Configure Environment
# Create .env file
DEPLOYER_MNEMONIC="your 25-word mnemonic"
ALGOD_TOKEN="your-algod-token"
ALGOD_SERVER="https://testnet-api.algonode.cloud"
2. Deploy Contract
npm run deploy:testnet
3. Note the App ID
The deployment script will output your application ID. Update your frontend configuration.
Tests individual contract methods in isolation using TestExecutionContext:
npm run test
Example test:
it('Creates invoice with correct initial state', () => {
const contract = ctx.contract.create(Innuid)
const invoiceId = contract.createInvoice(
buyer,
new UintN64(100_000_000),
new UintN64(5_000_000),
new UintN64(1735689600)
)
expect(invoiceId.native).toEqual(Uint64(0))
})
Tests complete user flows on LocalNet:
npm run test:e2e
Example flow:
1. Deploy contract
2. Create invoice as seller
3. Approve invoice as buyer
4. Invest as multiple investors
5. Pay invoice as buyer
6. Claim payouts as investors
const factory = algorand.client.getTypedAppFactory(InnuidFactory)
const { appClient } = await factory.deploy()
const result = await appClient.send.createInvoice({
sender: seller.addr,
args: {
buyer: buyer.addr.toString(),
totalAmount: 100_000_000n, // 100 ALGO
minInvestment: 5_000_000n, // 5 ALGO
dueDate: BigInt(Date.now() / 1000 + 30 * 86400), // 30 days
},
})
const invoiceId = result.return! // Invoice ID for tracking
// Create grouped transaction
const paymentTxn = await algorand.createTransaction.payment({
sender: investor.addr,
receiver: seller.addr,
amount: (50).algo(),
})
const investParams = await appClient.params.investInInvoice({
args: {
payment: paymentTxn,
invoiceId: 0n,
},
sender: investor.addr,
})
// Send atomic transaction
await algorand.send.appCallMethodCall(investParams)
const result = await appClient.send.claimPayout({
sender: investor.addr,
args: { invoiceId: 0n },
})
// Investor receives funds via inner transaction
// Investment automatically marked as claimed
TestNet Deployment:
- Application ID: 748002191
- Block Explorer: View on Lora
- Frontend URL: httpd://www.inuid.org
Contract State:
- Global State: 1 variable (
nextInvoiceId) - Box Storage: Dynamic allocation
- Total Deployed Size: ~2KB
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Write tests for new features
- Follow TypeScript best practices
- Use conventional commit messages
- Update documentation as needed
This project is licensed under the MIT License - see the LICENSE file for details.
- Developers: Sriram Sundar + Rudrrayan Manna
- Contacts: ss2d22@soton.ac.uk + rudrrayan@mgmail.com
- GitHubs: @Sriram @Rudrrayan
- Algorand Foundation - For the incredible blockchain platform
- AlgoKit Team - For the development framework
- shadcn/ui - For the beautiful component library
- Algorand Developer Portal
- AlgoKit Documentation
- Algorand TypeScript Docs
- Box Storage Guide
- Atomic Transactions
For questions, issues, or suggestions:
- Open an issue on GitHub Issues
- Join the Algorand Discord
- Contact us directly: @Sriram @Rudrrayan
Empowering businesses and investors through decentralized finance
View Contract on Explorer · Watch Demo · Report Bug · Request Feature
