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)
- Deploy with constructor parameters:
borrower,interestBasis(basis points), anddurationSeconds. - Lender calls
fund()sending the principal (msg.value). - Borrower calls
withdrawPrincipal()to get the loaned ETH. - Borrower repays by calling
repay()withprincipal + interest. - Lender calls
withdrawRepayment()to retrieve repayment.
Cheap deployment & testing steps
- Open Remix IDE (https://remix.ethereum.org). Use the Solidity compiler plugin set to
0.8.18(or compatible ^0.8.x). - Paste the Solidity code into a new file (e.g.,
SimpleLoan.sol), compile. - Use MetaMask and connect Remix’s Deploy tab -> Injected Provider. Use a testnet account with test ETH (Goerli/Sepolia). Testnets are cheaper than mainnet.
- 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). - Switch to borrower address in MetaMask, call
withdrawPrincipal(), then repay withrepay()sending computed amount. - 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
Post a Comment