A decentralized lending protocol built with Ink! v6 smart contracts for the Polkadot ecosystem. Kleo implements a reputation-based, community-vouched lending system where borrowers build creditworthiness through on-chain reputation and social guarantees.
Kleo Protocol enables undercollateralized lending through a combination of:
- Reputation System: Users earn "stars" that represent their creditworthiness
- Social Vouching: Community members stake their reputation and capital to vouch for borrowers
- Fixed Interest Rates: Interest rates are calculated and fixed at loan creation (stable coin compatible)
- Tiered Loan Requirements: Different loan amounts require different levels of reputation and vouches
- Dynamic Pool Rates: Lending pool interest rates adjust based on utilization
+------------------+
| Config |
| (Protocol Params)|
+--------+---------+
|
+--------------------+--------------------+
| | |
v v v
+-------+-------+ +-------+-------+ +-------+-------+
| Reputation | | Lending Pool | | Vouch |
| (Stars) | | (Liquidity) | | (Guarantees) |
+-------+-------+ +-------+-------+ +-------+-------+
| | |
+--------------------+--------------------+
|
v
+--------+---------+
| Loan Manager |
| (Orchestrator) |
+------------------+
All contracts reference the Config contract for protocol parameters. The Loan Manager orchestrates interactions between Reputation, Vouch, and Lending Pool contracts to facilitate the complete loan lifecycle.
Purpose: Central configuration storage for all protocol parameters.
Location: config/
Key Parameters:
| Parameter | Default Value | Description |
|---|---|---|
base_interest_rate |
10% | Base annual interest rate (scaled by 1e9) |
optimal_utilization |
80% | Target pool utilization for rate model |
slope1 |
4% | Interest rate increase below optimal utilization |
slope2 |
75% | Interest rate increase above optimal utilization |
boost |
2 | Bonus stars awarded for successful vouches |
min_stars_to_vouch |
50 | Minimum stars required to vouch for others |
cooldown_period |
60 seconds | New account cooldown before earning stars |
loan_term |
30 days | Default loan term |
exposure_cap |
5% | Maximum vouch exposure per borrower relative to pool |
reserve_factor |
20% | Portion of interest allocated to reserves |
max_rate |
100% | Maximum interest rate cap |
Key Functions:
new()- Initialize with default valuesset_admin(admin_account_id)- Set the admin accountupdate_*()- Admin-only setters for each parameterget_*()- Public getters for each parameter
Purpose: Manages user reputation through a star-based credit scoring system.
Location: reputation/
Storage:
- Per-user reputation tracking including stars, staked stars, loan history, vouch history, and ban status
Star System:
- New users start with 7 stars
- Stars accumulate over time after the cooldown period
- Stars can be staked when vouching for others
- Successful vouches return staked stars plus a bonus (configurable boost)
- Failed vouches result in loss of staked stars
- Users with 0 stars are banned from the protocol
Key Functions:
new(config_address, admin_account_id)- Initialize, deployer becomes adminget_stars(user)- Get current star count for a useradd_stars(user, amount)- Add stars to a user (respects cooldown)can_vouch(user)- Check if user meets minimum stars to vouchstake_stars(user, amount)- Lock stars for vouchingunstake_stars(user, amount, borrower, success)- Release staked stars with outcomeslash_stars(user, amount)- Penalty reduction of starsadmin_set_stars(user, stars)- Admin function to set starsadmin_add_stars(user, amount)- Admin function to add starsadmin_unban_user(user)- Admin function to unban a user
Purpose: Manages social guarantee relationships between users.
Location: vouch/
Vouch Relationship:
- Vouchers stake both stars (via Reputation) and capital (via Lending Pool)
- Multiple vouchers can support a single borrower
- Exposure is capped to prevent concentration risk
- Vouches are tracked per loan (not just per borrower)
Vouch Statuses:
Active- Vouch is currently backing an active loanFulfilled- Loan was repaid successfullyDefaulted- Loan defaulted, voucher penalized
Key Functions:
new(config_address, reputation_address, lending_pool_address)- Initializeset_loan_manager(loan_manager_address)- Set authorized loan managervouch_for_loan(loan_id, borrower, voucher, stars, capital_percent, loan_manager_address)- Create a vouch for a specific loanget_vouches_for_loan(loan_id)- Count active vouches for a loanget_vouchers_for_loan(loan_id)- List all voucher addresses for a loanresolve_loan(loan_id, borrower, success, loan_manager_address)- Settle all vouches when loan concludesresolve_all(borrower, success, loan_manager_address)- Settle all vouches for a borrower (backward compatibility)
Events:
VouchCreated- New vouch relationship establishedVouchResolved- Vouch settled with success/failure outcome
Purpose: Manages liquidity pool with automatic interest rate calculations and accruals.
Location: lending_pool/
Interest Rate Model: The pool uses a two-slope interest rate model:
if utilization <= optimal:
rate = base + (utilization / optimal) * slope1
else:
rate = base + slope1 + ((utilization - optimal) / (1 - optimal)) * slope2
This encourages deposits when utilization is high and borrowing when utilization is low.
Key Functions:
new(config_address)- Initializeset_vouch_contract(vouch_address)- Set authorized vouch contractset_loan_manager(loan_manager_address)- Set authorized loan managerdeposit(account_id)- Add liquidity to the pool (payable, accepts 18 decimals)withdraw(amount, account_id)- Remove liquidity from the pool (amount in 10 decimals)disburse(amount, to)- Transfer funds for approved loans (only loan manager, amount in 10 decimals)receive_repayment(amount)- Process loan repayments (payable, amount in 18 decimals)slash_stake(user, amount)- Penalize voucher deposits on default (only vouch contract, amount in 10 decimals)get_current_rate()- Calculate current interest rateget_user_deposit(user)- Query user deposit balance (returns 10 decimals)get_user_yield(account_id)- Calculate accrued yield for a user (read-only, returns 18 decimals)accrue_interest_and_get_user_yield(account_id)- Accrue interest then calculate yield (returns 18 decimals)get_total_liquidity()- Query total pool liquidity (returns 18 decimals)
Events:
Deposit- Funds added to poolWithdraw- Funds removed from poolRepaymentReceived- Loan repayment processed
Purpose: Central orchestrator that coordinates all contracts to manage the complete loan lifecycle.
Location: loan_manager/
Loan Tiers:
| Tier | Loan Size | Min Stars | Min Vouches |
|---|---|---|---|
| Tier 1 | < 1,000 units | 5 | 1 |
| Tier 2 | 1,000 - 10,000 units | 20 | 2 |
| Tier 3 | > 10,000 units | 50 | 3 |
Interest Rate Adjustment: Borrowers with higher reputation receive discounted rates:
- Each star reduces the rate by 1% (configurable)
- Maximum discount is 50% (configurable)
Interest Rate Calculation:
- Interest rates are fixed at loan creation (not time-based)
- Repayment =
amount × (1 + interest_rate_percentage) - Example: 100 tokens at 10% = 110 tokens repayment
- The repayment amount is stored in the loan and can be queried
Loan Statuses:
Pending- Loan requested, waiting for vouchesActive- Loan disbursed, borrower must repayRepaid- Loan fully repaidDefaulted- Loan term expired without repayment
Key Functions:
new(config_address, reputation_address, lending_pool_address, vouch_address)- Initializerequest_loan(amount, loan_term, account_id)- Apply for a new loanvouch_for_loan(loan_id, stars, capital_percent, voucher_account_id, loan_manager_address)- Vouch for a pending loanrepay_loan(loan_id, borrower_account_id, loan_manager_address)- Repay an active loan (payable)check_default(loan_id, loan_manager_address, vouch_contract_address)- Process overdue loansget_loan(loan_id)- Get loan informationget_repayment_amount(loan_id)- Get the fixed repayment amount for a loanget_all_pending_loans()- Get all loan IDs with Pending statusget_all_active_loans()- Get all loan IDs with Active status
Loan Request Flow:
- Validate amount is non-zero
- Calculate tier-based requirements
- Verify borrower has sufficient stars (Reputation)
- Fetch current rate from lending pool
- Adjust rate based on borrower's stars
- Calculate fixed repayment amount:
amount × (1 + interest_rate_percentage) - Create loan record with Pending status
- Emit
LoanRequestedevent
Loan Disbursement Flow:
- Vouchers call
vouch_for_loanto stake stars and capital - When minimum vouches threshold is met, loan auto-disburses
- Loan status changes to Active
- Funds transferred to borrower via Lending Pool
Repayment Flow:
- Borrower calls
repay_loanwith exact repayment amount - Funds transferred to Lending Pool
- Loan status changes to Repaid
- All vouches resolved as successful
- Vouchers receive staked stars back plus bonus
- Emit
LoanRepaidevent
Default Processing Flow:
- Anyone can call
check_defaultfor an overdue loan - Verify loan exists and is active
- Check if loan term has expired (with grace period)
- Mark loan as defaulted
- Slash borrower stars (Reputation)
- Resolve all vouches as failed (Vouch)
- Slash voucher capital (Lending Pool)
- Emit
LoanDefaultedevent
Events:
LoanRequested- New loan createdLoanRepaid- Loan successfully repaidLoanDefaulted- Loan defaulted after term expiration
- Deposit: Call
lending_pool.deposit(account_id)with native tokens - Earn Yield: Interest accrues automatically based on pool utilization
- Vouch (Optional): Stake stars and capital to vouch for borrowers
- Withdraw: Call
lending_pool.withdraw(amount, account_id)to retrieve funds
- Build Reputation: Accumulate stars over time (starts with 7 stars)
- Get Vouches: Find community members willing to vouch
- Request Loan: Call
loan_manager.request_loan(amount, loan_term, account_id) - Wait for Disbursement: Loan auto-disburses when enough vouches collected
- Repay: Call
loan_manager.repay_loan(loan_id, borrower_account_id, loan_manager_address)with exact repayment amount before term expires - Default Risk: If loan expires unpaid, stars are slashed and vouchers penalized
- Qualify: Accumulate at least 50 stars (configurable)
- Deposit Capital: Provide liquidity to the lending pool
- Vouch: Call
loan_manager.vouch_for_loan(...)to stake stars and capital - Outcome:
- Success: Receive staked stars back plus bonus stars (default +2)
- Default: Lose staked stars and capital portion
Contracts must be deployed in the following order due to dependencies:
- Config - No dependencies
- Reputation - Requires Config address
- Lending Pool - Requires Config address
- Vouch - Requires Config, Reputation, and Lending Pool addresses
- Loan Manager - Requires Config, Reputation, Lending Pool, and Vouch addresses
After deployment, you must set up contract references:
- Config.set_admin(admin_account_id) - Set the admin account
- Reputation.set_vouch_contract(vouch_address) - Set vouch contract reference
- Reputation.set_loan_manager(loan_manager_address) - Set loan manager reference
- LendingPool.set_vouch_contract(vouch_address) - Set vouch contract reference
- LendingPool.set_loan_manager(loan_manager_address) - Set loan manager reference
- Vouch.set_loan_manager(loan_manager_address) - Set loan manager reference
Important: Pay attention to whether functions require Address (H160) or AccountId (32-byte) types.
Each contract can be built using cargo-contract:
# Build individual contract
cd config && cargo contract build --release
cd reputation && cargo contract build --release
cd lending_pool && cargo contract build --release
cd vouch && cargo contract build --release
cd loan_manager && cargo contract build --releaseOr build all at once:
./demo.shBuild artifacts are output to target/ink/ in each contract directory:
*.contract- Bundled contract (metadata + wasm)*.json- Contract metadata/ABI*.polkavm- PolkaVM bytecode
See CLI_TESTING_GUIDE.md for detailed testing instructions.
Quick test flow:
- Deploy all contracts in order
- Set up contract references
- Bootstrap test accounts with stars
- Add liquidity to pool
- Request a loan
- Vouch for the loan
- Repay the loan
- Verify outcomes
- Interest rates are calculated and fixed at loan creation
- Compatible with stable coins (no time-based accrual)
- Repayment amount =
principal × (1 + interest_rate_percentage) - Query repayment amount with
get_repayment_amount(loan_id)
- Many functions now require
account_idas a parameter (temporary workaround forto_account_idissues) - Functions that check authorization use
caller()internally - Pay attention to function signatures when calling
- Storage format (10 decimals):
user_depositsare stored in 10 decimals - Chain format (18 decimals): All transfers and
total_liquidityuse 18 decimals - Function parameters:
deposit(): Accepts value in 18 decimals (fromtransferred_value())withdraw(amount, account_id):amountparameter in 10 decimalsdisburse(amount, to):amountparameter in 10 decimalsreceive_repayment(amount):amountparameter in 18 decimals (matchestransferred_value())get_repayment_amount(loan_id): Returns 18 decimals (chain format)get_user_yield(account_id): Returns 18 decimals (chain format)get_total_liquidity(): Returns 18 decimals (chain format)
get_all_pending_loans()- Get all pending loan IDsget_all_active_loans()- Get all active loan IDsget_repayment_amount(loan_id)- Get fixed repayment amount (returns 18 decimals)get_user_yield(account_id)- Get user yield without accruing interest (read-only, returns 18 decimals)accrue_interest_and_get_user_yield(account_id)- Get user yield with interest accrual (returns 18 decimals)
Each contract defines specific error types:
Config: NotAdmin, InvalidValue, AlreadyAdmin
Reputation: UserNotFound, InsufficientStars, InsufficientStakedStars, UserBanned, Unauthorized
Vouch: NotEnoughStars, NotEnoughCapital, UnableToVouch, ZeroAmount, ExposureCapExceeded, AlreadyResolved, RelationshipNotFound, Unauthorized
Lending Pool: ZeroAmount, NegativeAmount, Overflow, UnavailableFunds, TransactionFailed, AmountMismatch, Unauthorized
Loan Manager: InsufficientReputation, InsufficientVouches, ZeroAmount, DisbursementFailed, LoanNotFound, LoanNotActive, LoanNotPending, LoanNotOverdue, SlashFailed, ResolveFailed, Unauthorized, RepaymentFailed, InvalidRepaymentAmount, Overflow
MIT License