Skip to content
L

loaniyo

"Grow Your Money, Control Your Future" with decent

Created on 16th August 2025

•

L

loaniyo

"Grow Your Money, Control Your Future" with decent

The problem loaniyo solves

Traditional banks offer terrible savings rates (0.1-0.5% APY) while charging high fees and maintaining full control over your funds. Getting loans requires weeks of paperwork, credit checks, and hidden costs. Meanwhile, existing DeFi platforms are too complex for everyday users.
The Solution
Loaniyo bridges traditional finance and DeFi by providing a user-friendly lending platform on Base blockchain.

Challenges I ran into

  1. Network Configuration Complexity
    Challenge: Initially configured for Ethereum mainnet, but needed Base Sepolia testnet deployment. Complex chain ID conflicts and RPC endpoint issues caused deployment failures.

Solution: Systematically updated hardhat.config.js, wagmi.ts, and contract addresses. Created dedicated deployment scripts for Base Sepolia with proper network validation.

  1. Wallet Integration State Management
    Challenge: Hydration mismatches between server and client rendering caused wallet connection to break. React state wasn't properly syncing with wallet connection status.

Solution: Implemented proper useEffect mounting checks and useState for hydration safety. Added loading states and error boundaries to handle connection edge cases gracefully.

  1. Smart Contract Deployment Dependencies
    Challenge: Deployment failed due to insufficient testnet ETH for gas fees. The deployment script couldn't proceed without proper funding verification.

Solution: Enhanced deployment script to check balances first, provide clear funding instructions, and validate network connection before attempting deployment. Added helpful error messages directing users to Base Sepolia faucets.

  1. UI State Synchronization
    Challenge: Dashboard tabs (withdraw/history) changed visual state but showed no content, creating confusing UX.

Solution: Implemented proper tab content rendering with conditional state management, ensuring visual feedback matched functional behavior.

Key Lesson: Always validate end-to-end user flows, not just individual component functionality.

Smart Contract Address(es)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract LoaniyoLending is ReentrancyGuard, Ownable {
struct User {
uint256 deposited;
uint256 borrowed;
uint256 lastUpdateTime;
uint256 accruedInterest;
}

IERC20 public immutable token; // The token being lent/borrowed (e.g., USDC)

mapping(address => User) public users;

uint256 public totalDeposits;
uint256 public totalBorrows;
uint256 public interestRate = 500; // 5% annual interest rate (in basis points)
uint256 public collateralRatio = 15000; // 150% collateralization ratio (in basis points)

uint256 private constant SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
uint256 private constant BASIS_POINTS = 10000;

event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event Borrow(address indexed user, uint256 amount);
event Repay(address indexed user, uint256 amount);

constructor(address _token) Ownable(msg.sender) {
    token = IERC20(_token);
}

modifier updateInterest(address user) {
    _updateUserInterest(user);
    _;
}

function _updateUserInterest(address user) internal {
    User storage userData = users[user];
    if (userData.borrowed > 0 && userData.lastUpdateTime > 0) {
        uint256 timeElapsed = block.timestamp - userData.lastUpdateTime;
        uint256 interest = (userData.borrowed * interestRate * timeElapsed) / (BASIS_POINTS * SECONDS_PER_YEAR);
        userData.accruedInterest += interest;
    }
    userData.lastUpdateTime = block.timestamp;
}

function deposit(uint256 amount) external nonReentrant updateInterest(msg.sender) {
    require(amount > 0, "Amount must be greater than 0");
    require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
    
    users[msg.sender].deposited += amount;
    totalDeposits += amount;
    
    emit Deposit(msg.sender, amount);
}

function withdraw(uint256 amount) external nonReentrant updateInterest(msg.sender) {
    require(amount > 0, "Amount must be greater than 0");
    require(users[msg.sender].deposited >= amount, "Insufficient deposits");
    
    // Check if withdrawal would make user undercollateralized
    uint256 newDeposited = users[msg.sender].deposited - amount;
    uint256 totalOwed = users[msg.sender].borrowed + users[msg.sender].accruedInterest;
    
    if (totalOwed > 0) {
        require(
            newDeposited * BASIS_POINTS >= totalOwed * collateralRatio,
            "Withdrawal would make position undercollateralized"
        );
    }
    
    users[msg.sender].deposited -= amount;
    totalDeposits -= amount;
    
    require(token.transfer(msg.sender, amount), "Transfer failed");
    
    emit Withdraw(msg.sender, amount);
}

function borrow(uint256 amount) external nonReentrant updateInterest(msg.sender) {
    require(amount > 0, "Amount must be greater than 0");
    require(amount <= getAvailableLiquidity(), "Insufficient liquidity");
    
    User storage userData = users[msg.sender];
    uint256 newBorrowed = userData.borrowed + amount;
    uint256 totalOwed = newBorrowed + userData.accruedInterest;
    
    // Check collateralization
    require(
        userData.deposited * BASIS_POINTS >= totalOwed * collateralRatio,
        "Insufficient collateral"
    );
    
    userData.borrowed += amount;
    totalBorrows += amount;
    
    require(token.transfer(msg.sender, amount), "Transfer failed");
    
    emit Borrow(msg.sender, amount);
}

function repay(uint256 amount) external nonReentrant updateInterest(msg.sender) {
    require(amount > 0, "Amount must be greater than 0");
    require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
    
    User storage userData = users[msg.sender];
    uint256 totalOwed = userData.borrowed + userData.accruedInterest;
    require(totalOwed > 0, "No debt to repay");
    
    if (amount >= totalOwed) {
        // Full repayment
        userData.borrowed = 0;
        userData.accruedInterest = 0;
        totalBorrows -= userData.borrowed;
        
        // Refund excess
        if (amount > totalOwed) {
            require(token.transfer(msg.sender, amount - totalOwed), "Refund failed");
        }
    } else {
        // Partial repayment - pay interest first
        if (amount >= userData.accruedInterest) {
            uint256 principalPayment = amount - userData.accruedIn

Tracks Applied (1)

Top 5 New Mini Apps

https://loaniyo.vercel.app/

Cheer Project

Cheering for a project means supporting a project you like with as little as 0.0025 ETH. Right now, you can Cheer using ETH on Arbitrum, Optimism and Base.

Discussion

Builders also viewed

See more projects on Devfolio