Search projects, hackathons, sponsors and tech

    ← All projects

    loaniyo

    "Grow Your Money, Control Your Future" with decent

    About

    The problem it 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. 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

    How it's made

    Challenges we 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. 2. 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. 3. 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. 4. 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.