Crypto Miner

Cheap Crypto Loan Smart Contract (Simple Example + How to Deploy)

This post shows a compact, low-cost example smart contract for a single loan agreement on Ethereum-style networks (EVM). It’s intentionally minimal to keep gas and complexity down. Use it for learning and testing on testnets (Goerli / Sepolia) — do not use for production funds without auditing.

What this contract does (simple flow)

  • Lender deploys the contract or funds it with the principal amount.
  • Borrower accepts and withdraws the principal.
  • Borrower repays principal + interest before deadline.
  • Lender withdraws the repayment.

Why “cheap”?

This design is gas-conscious by being single-loan (not a pool), avoiding external library imports, and using native ETH instead of ERC-20 to reduce complexity. Again, that also limits features and safety.

Solidity contract (paste into Remix)



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

/// @title SimpleLoan - Minimal single-loan contract (educational only)

/// @notice Very small, low-gas example. NOT audited. Use on testnets only.

contract SimpleLoan {

    address public lender;

    address public borrower;

    uint256 public principal;          // amount of loan in wei

    uint256 public interestBasis;      // interest as basis points (100 bps = 1%)

    uint256 public dueTimestamp;       // unix timestamp when loan is due

    bool public funded;

    bool public withdrawnByBorrower;

    bool public repaid;

    // Basic reentrancy guard

    uint256 private _locked = 1;

    modifier nonReentrant() {

        require(_locked == 1, "reentrancy");

        _locked = 2;

        _;

        _locked = 1;

    }

    event Funded(address indexed lender, uint256 amount);

    event WithdrawnByBorrower(address indexed borrower, uint256 amount);

    event Repaid(address indexed borrower, uint256 amount);

    event WithdrawnByLender(address indexed lender, uint256 amount);

    /// @param _borrower address of the borrower

    /// @param _interestBasis interest in basis points (e.g., 500 = 5%)

    /// @param _durationSeconds loan duration in seconds from funding

    constructor(address _borrower, uint256 _interestBasis, uint256 _durationSeconds) {

        require(_borrower != address(0), "invalid borrower");

        require(_interestBasis <= 50000, "interest too high"); // safe guard

        lender = msg.sender;       // deployer is lender by default

        borrower = _borrower;

        interestBasis = _interestBasis;

        dueTimestamp = block.timestamp + _durationSeconds;

    }

    /// @notice Lender funds the contract with the principal amount.

    function fund() external payable {

        require(msg.sender == lender, "only lender");

        require(!funded, "already funded");

        require(msg.value > 0, "principal required");

        principal = msg.value;

        funded = true;

        dueTimestamp = block.timestamp + (dueTimestamp - (block.timestamp - (dueTimestamp - block.timestamp)));

        emit Funded(msg.sender, msg.value);

    }

    /// @notice Borrower withdraws the principal once funded.

    function withdrawPrincipal() external nonReentrant {

        require(funded, "not funded");

        require(msg.sender == borrower, "only borrower");

        require(!withdrawnByBorrower, "already withdrawn");

        withdrawnByBorrower = true;

        (bool ok,) = borrower.call{value: principal}("");

        require(ok, "transfer failed");

        emit WithdrawnByBorrower(borrower, principal);

    }

    /// @notice Borrower repays loan (send msg.value = principal + interest)

    function repay() external payable nonReentrant {

        require(funded, "not funded");

        require(withdrawnByBorrower, "not withdrawn yet");

        require(!repaid, "already repaid");

        require(msg.sender == borrower, "only borrower");

        uint256 interest = (principal * interestBasis) / 10000; // basis points calc

        uint256 total = principal + interest;

        require(msg.value >= total, "insufficient repayment");

        repaid = true;

        // surplus stays in contract for lender withdrawal

        emit Repaid(msg.sender, msg.value);

    }

    /// @notice Lender withdraws repayment (principal + interest) after repay

    function withdrawRepayment() external nonReentrant {

        require(msg.sender == lender, "only lender");

        require(repaid, "not repaid");

        uint256 bal = address(this).balance;

        require(bal > 0, "no balance");

        (bool ok,) = lender.call{value: bal}("");

        require(ok, "transfer failed");

        emit WithdrawnByLender(lender, bal);

    }

    /// @notice If borrower did not repay and deadline passed, lender can reclaim principal (if not withdrawn by borrower)

    function reclaimIfUnwithdrawn() external nonReentrant {

        require(msg.sender == lender, "only lender");

        require(block.timestamp > dueTimestamp, "not due yet");

        require(funded, "not funded");

        require(!withdrawnByBorrower, "borrower already withdrew");

        uint256 bal = address(this).balance;

        require(bal > 0, "no balance");

        (bool ok,) = lender.call{value: bal}("");

        require(ok, "transfer failed");

        emit WithdrawnByLender(lender, bal);

    }

    // Fallback to accept repayment if directly sent

    receive() external payable {}

}

How it works (quick)

  1. Deploy with constructor parameters: borrower, interestBasis (basis points), and durationSeconds.
  2. Lender calls fund() sending the principal (msg.value).
  3. Borrower calls withdrawPrincipal() to get the loaned ETH.
  4. Borrower repays by calling repay() with principal + interest.
  5. Lender calls withdrawRepayment() to retrieve repayment.

Cheap deployment & testing steps

  1. Open Remix IDE (https://remix.ethereum.org). Use the Solidity compiler plugin set to 0.8.18 (or compatible ^0.8.x).
  2. Paste the Solidity code into a new file (e.g., SimpleLoan.sol), compile.
  3. Use MetaMask and connect Remix’s Deploy tab -> Injected Provider. Use a testnet account with test ETH (Goerli/Sepolia). Testnets are cheaper than mainnet.
  4. Deploy contract as lender address: set borrower, interest basis (e.g., 500 for 5%), and duration (e.g., 604800 for 1 week). Then click fund() and send the principal amount (e.g., 0.1 ETH).
  5. Switch to borrower address in MetaMask, call withdrawPrincipal(), then repay with repay() sending computed amount.
  6. Lender withdraws repayment with withdrawRepayment().

Gas-saving tips

  • Test and iterate on testnets. Gas costs on mainnet are significant.
  • Keep contract logic simple and minimize storage writes. Each storage write is expensive.
  • Batch administrative actions off-chain where possible.

Security & limitations — read carefully

  • This contract is minimal and not audited. It lacks many real-world protections (no collateral handling, no ERC-20 support, no dispute resolution, no oracle for interest rates, no robust failure handling).
  • Using native ETH simplifies code but constrains usefulness (most protocol activity uses ERC-20 tokens). Implementing ERC-20 support safely requires approved/trusted token handling.
  • Do not deploy on mainnet with real funds unless audited by professionals. Smart contracts are irreversible once live.
  • Consider OpenZeppelin libraries, thorough unit tests, fuzzing, and professional audits before production use.

Wrap-up

This post gives a starting point for a low-cost, single-loan smart contract suitable for learning and testnet experimentation. If you want I can:

  • Provide an ERC-20 version so loans use tokens instead of native ETH.
  • Create a multi-loan pool design (costs more gas but more practical).
  • Give you a ready-to-publish Blogger post with screenshots and step-by-step screenshots.

Reminder: educational only — not financial or legal advice.

Comments