Skip to content

Repository files navigation

🌾 AgriVault

CI License: MIT Built on Stellar Soroban PRs Welcome

Tokenized warehouse receipts and commodity financing for smallholder farmers — built on Stellar Soroban.

A farmer deposits stored crops in a certified warehouse, gets a tokenized receipt, and instantly borrows USDC against it — instead of panic-selling at the bottom of the market.


The Problem

Smallholder farmers are cash-poor right after harvest. They dump crops when prices are lowest because they need liquidity, then can't access credit because they have no collateral a bank recognizes. Billions in stored grain sit as "dead" capital. A warehouse receipt — proof you own X tonnes in a certified store — is a classic real-world asset, but today it is paper, unverifiable, and untradeable.

AgriVault turns warehouse receipts into programmable on-chain collateral.


How It Works

┌─────────────────────────────────────────────────────────────────────┐
│  Warehouse Operator                                                 │
│  (approved attestor)                                                │
│       │ mint(receipt_meta)                                          │
│       ▼                                                             │
│  receipt_token ──── KYC-gated transfer ────► Farmer wallet         │
│       │                                           │                 │
│       │                              lock(receipt_id)              │
│       │                                           ▼                 │
│       └──────────────────────────────────► lending_pool            │
│                                                   │                 │
│  Lender                                           │ borrow(amount) │
│  deposit(USDC) ──────────────────────────────────►│                │
│                                                   │                 │
│                                      USDC (≤60% LTV)               │
│                                           ▼                         │
│                                      Farmer wallet                  │
│                                                                     │
│  On repay: receipt unlocked, interest accrues to lenders           │
│  On default: anyone calls liquidate() after 90 days                │
└─────────────────────────────────────────────────────────────────────┘

Repository Layout

agrivault/
│
├── contracts/                    # Soroban smart contracts (Rust)
│   ├── attestor_registry/        # Admin-controlled list of approved warehouse operators
│   ├── receipt_token/            # Permissioned receipt token with KYC gate + lock/unlock
│   └── lending_pool/             # USDC pool — borrow, repay, liquidate
│
├── app/                          # Next.js 14 frontend
│   └── src/
│       ├── app/                  # App Router pages
│       │   ├── operator/         # Issue receipts
│       │   ├── farmer/           # Lock, borrow, repay
│       │   └── lender/           # Deposit, earn, withdraw
│       ├── components/           # Shared UI components
│       ├── context/              # WalletContext (Freighter v2)
│       ├── lib/                  # contracts.ts, constants.ts, demo.ts
│       └── types/                # Canonical TypeScript interfaces
│
├── indexer/                      # Node.js event indexer → Postgres → REST API
│   └── src/
│       ├── db.ts                 # Schema + migrations
│       ├── poller.ts             # Soroban event polling
│       ├── processor.ts          # Event → database
│       └── api.ts                # Express REST endpoints
│
├── sdk/                          # TypeScript SDK wrapping all three contracts
│   └── src/
│       ├── client.ts             # SorobanClient (build, simulate, submit)
│       └── contracts/            # AttestorRegistry, ReceiptToken, LendingPool clients
│
├── scripts/                      # Testnet deploy + seed scripts
│   └── src/
│       ├── deploy.ts             # Deploy contracts, initialize, wire together
│       └── seed.ts               # Fund accounts, mint demo receipt, borrow USDC
│
├── Cargo.toml                    # Rust workspace
└── README.md

Smart Contracts

attestor_registry

Controls which warehouse operators are allowed to mint receipt tokens.

Function Auth Description
initialize(admin) One-time setup
approve_attestor(attestor) admin Whitelist a warehouse operator
revoke_attestor(attestor) admin Remove from whitelist
is_attestor(attestor) → bool Read-only check
set_admin(new_admin) admin Transfer admin role

receipt_token

A permissioned token representing a physical warehouse deposit.

Function Auth Description
mint(attestor, owner, commodity, grade, tonnes, valuation_usd, warehouse_id, ipfs_hash) → u64 approved attestor Mint receipt for KYC'd owner
kyc_add(wallet) / kyc_remove(wallet) admin Manage KYC allowlist
transfer(from, to, receipt_id) owner Both parties must be KYC'd; receipt must be unlocked
lock(caller, receipt_id) lending_pool only Lock as collateral
unlock(caller, receipt_id) lending_pool only Release after repayment
mark_redeemable(caller, receipt_id) lending_pool only Mark after liquidation
get_receipt(id) → ReceiptMeta Read metadata

Encoding:

  • tonnes is stored as tonnes × 100 (e.g. 2000 = 20.00 t)
  • valuation_usd is stored as USD cents (e.g. 400000 = $4,000.00)

lending_pool

USDC lending pool with LTV-enforced borrowing and pro-rata interest distribution.

Function Auth Description
initialize(admin, usdc_token, receipt_token, ltv_bps, annual_rate_bps, loan_duration) One-time setup
deposit(lender, amount) lender Add USDC liquidity
withdraw(lender, amount) lender Remove USDC proportional to pool balance
borrow(farmer, receipt_id, amount) receipt owner Lock receipt, receive USDC (≤ LTV)
repay(farmer, receipt_id) borrower Pay principal + interest, unlock receipt
liquidate(caller, receipt_id) anyone Trigger after loan duration expires
pool_info() → PoolInfo Pool stats snapshot
amount_due(receipt_id) → i128 Current repayment obligation

Economics (defaults — all configurable at initialization):

Parameter Default Meaning
LTV 60% Maximum borrow = valuation × 0.60
Annual rate 8% Simple (non-compounding) linear interest
Loan duration 90 days Loan becomes liquidatable after this
USDC precision 6 dp 1 USDC = 1,000,000 stroops

Interest formula:

interest = principal × (rate_bps / 10_000) × (elapsed_seconds / 31_536_000)

Frontend — Three Views

🏭 Operator (/operator)

Warehouse operators fill a form specifying commodity, grade, quantity, valuation, and IPFS document hash. Clicking "Issue Receipt" calls mint() via Freighter. The page previews the exact amount the farmer will be able to borrow before submission.

👨‍🌾 Farmer (/farmer)

Farmers look up a receipt by ID. If they own it and it's unlocked, they can enter a borrow amount (or hit Max for 60% LTV), which calls borrow() and locks the receipt. The page shows the active loan details and a Repay button to call repay() and reclaim the receipt.

🏦 Lender (/lender)

Lenders see pool stats (total deposits, balance, utilisation, interest earned), their own deposit, and estimated earnings. One-click quick-fill buttons for $1k / $5k / $10k deposits. Withdraw button with Max.


Tech Stack

Layer Technology
Smart contracts Rust · Soroban SDK 21 · wasm32-unknown-unknown
Frontend Next.js 14 (App Router) · TypeScript · Tailwind CSS
Wallet Freighter v2 (@stellar/freighter-api)
Stellar SDK @stellar/stellar-sdk 12
Indexer Node.js · Express · PostgreSQL · pg
Deploy scripts ts-node · Stellar CLI

Getting Started

Prerequisites

Tool Install
Rust 1.75+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
wasm target rustup target add wasm32-unknown-unknown
Stellar CLI cargo install --locked stellar-cli --features opt
Node.js 20+ nodejs.org
PostgreSQL 14+ postgresql.org
Freighter wallet freighter.app

1 — Clone and install

git clone <repo-url>
cd agrivault

# Install all JS packages
cd app     && npm install && cd ..
cd indexer && npm install && cd ..
cd scripts && npm install && cd ..
cd sdk     && npm install && cd ..

2 — Run in demo mode (no blockchain required)

cd app
cp .env.local.example .env.local
# NEXT_PUBLIC_DEMO_MODE=true is already set in .env.local.example
npm run dev
# → http://localhost:3000

Demo mode simulates all transactions with 1-second fake round-trips so you can explore the full UI without deploying contracts.

3 — Build contracts (requires Rust + Stellar CLI)

# From the repo root
cargo build --release --target wasm32-unknown-unknown

4 — Deploy to Testnet

cd scripts
cp .env.example .env
# Edit .env — add ADMIN_SECRET (a funded Testnet keypair secret key)

npm run deploy
# Deploys all three contracts, initializes them, and writes deployed.env

5 — Seed demo data

npm run seed
# Creates keypairs, funds via Friendbot, mints a 20t Maize receipt,
# lender deposits $10k, farmer borrows $2,400, demonstrates KYC revert

6 — Run the indexer

cd indexer
cp .env.example .env
# Fill DATABASE_URL, contract IDs from deployed.env

npm run dev
# REST API → http://localhost:3001

7 — Run the app against live contracts

cd app
cp deployed.env .env.local     # or copy the relevant vars
# Make sure NEXT_PUBLIC_DEMO_MODE is NOT set (or set to false)

npm run dev
# → http://localhost:3000

REST API Reference

Base URL: http://localhost:3001

Method Endpoint Query params Description
GET /receipts owner, locked, redeemable, limit, offset List receipts
GET /receipts/:id Single receipt
GET /loans farmer, repaid, liquidated, limit, offset List loans
GET /loans/:receiptId Single loan
GET /pool Pool aggregate stats
GET /pool/lenders/:address Lender's deposit balance
GET /events type, contract, limit, offset Raw event log
GET /health Service health check

Unit Tests

# From the repo root
cargo test

Test coverage:

Contract What's tested
attestor_registry Initialize, double-init guard, approve, revoke, admin-auth enforcement, admin transfer
receipt_token Mint by approved attestor, unapproved attestor rejected, non-KYC owner rejected, KYC transfer gate, transfer to KYC wallet, lock/unlock, lock-only-by-pool guard, locked receipt not transferable, mark redeemable, counter increments
lending_pool Deposit + balance, borrow within LTV, borrow exceeding LTV rejected, repay + unlock, liquidate before due date rejected, liquidate overdue, lender balance grows after repay, interest calculation, double-borrow on same receipt rejected

Security Notes

  • KYC gate: Receipt transfers are gated by an admin-managed allowlist. Sending to a non-KYC address reverts.
  • Attestor gate: Only addresses approved in attestor_registry can mint receipts. Unapproved attestors revert.
  • Pool-only lock/unlock: The receipt_token contract will only accept lock/unlock/mark_redeemable calls from the registered lending_pool contract address.
  • LTV enforcement: The pool enforces amount ≤ (valuation_usd_cents × LTV_bps / 10_000) × 10_000 at borrow time.
  • Permissionless liquidation: Anyone can call liquidate() after the loan duration expires — no oracle or keeper bot required.
  • Interest model: Simple (non-compounding) linear interest — no runaway debt spirals.
  • Secret management: deployed.env and seed-keys.json are in .gitignore — never commit private keys.

Out of Scope (MVP)

Feature Status
Real warehouse IoT / physical inspection Out of scope
Price oracles (valuation is admin-set at mint) Out of scope
Secondary receipt trading on a DEX Out of scope
Multi-collateral loans Out of scope
Compounding interest Out of scope
Mobile wallet support Out of scope

Demo Scenario

1. Operator  → issue receipt: "20 t Maize · Grade A · WH001 · $4,000"
               Receipt #0 minted to farmer wallet

2. Farmer    → load receipt #0 → click Max → Lock Receipt & Borrow USDC
               $2,400 USDC lands in farmer wallet instantly
               Receipt is now locked (not transferable)

3. [Time passes — 90 days elapse on-chain]

4. Anyone    → liquidate(receipt_id=0)
               Receipt marked redeemable
               Lenders' total_deposits grows with accrued interest

5. Farmer    → try transfer(receipt=0, non-KYC wallet)
               Transaction REVERTS ✓ ("recipient not KYC'd")

Built for the Stellar Community Fund / CV Labs Accelerator — RWA Financial Inclusion track.

About

AgriVault — Tokenized warehouse receipts for smallholder farmers on Stellar Soroban. Farmers deposit crops, receive on-chain receipt tokens, and borrow USDC instantly (60% LTV) instead of panic-selling at harvest lows. Lenders earn 8% APR. KYC-gated transfers, permissionless liquidation after 90 days

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages