person in red sweater holding babys hand

I was entertaining the idea of making a payable NFT. An NFT is a smart contract, and can be made payable.

Why would I want to pay to an NFT ? Because then I can turn an NFT collection into a loan.

I can make an NFT : 10.000 max, 0.001 Eth each (20 dollars), 4% yearly interest, 20 year linear amortization, 500 NFT’s per year. My friends can buy the NFT, and pay me 0.001 ETH a piece, 10 Eth in total. And over the next 20 years, I pay yearly interest and amortization to the NFT collection. The current NFT holder gets the payments.

Thus I can ‘borrow’ 20.000 dollars from my friends and backers in stead of the bank or VC.

NFT’s can be traded on OpenSea. So there is always an opportunity to liquidate and sell your NFT.

Fan Finance

The DeFi aspect in it, the bank cannot offer you a loan with your own friends and family as backers. You get ‘general conditions’ (7%) where your friends might settle for 4%.

If you are famous, you can ‘game’ the collection. It creates a link between you the holders. And you can for instance increase the perceived value of the NFT by dropping ‘exclusive fan contact’ to the NFT holders. Your fans will go crazy and all want that LoanNFT. You can put 5% royalties on the NFT and make a nice side income.

I like the ‘game’ aspect of it.

It is a way to ‘remove the middle man’ and ‘come closer’.


At the bank I currently pay 7%, and over the duration of the loan that equates to ~70% interest. 14.000 dollars interest. At 4% I pay ~40% interest, 8000 dollars. That is a 6000 dollar saving. The interest goes to my backing, and not to the bank. You keep the money ‘in the club’.


It comes at a cost.

A contract requires 96.000 payments and at 2 cents for basic transactions on Polygon, that will cost ~2000 dollars. The savings is 30%, so above 7000 dollars it is cheaper to finance a loan this way compared to paying 7% to the bank.

Viable ?

Considering costs and possible savings, I would say it is worth having a look at.


This is my current code. It can be compiled, but that’s about all. It is only half working. It can be compiled, but that’s it. I am ashamed of this :) But I am rather enthousiast about the idea, so I figure I’d just throw the idea in the open, and maybe people pick up on it. I cordially invite you and anyone that knows Solidity to make a ten times better, smarter and more cost effective version.

contract LoanNFT is ERC721, Ownable(msg.sender) { 
    using SafeMath for uint256;

    uint256 public constant MAX_SUPPLY = 10000;
    uint256 public constant PRICE_PER_NFT = 0.001 ether;

    uint256 public constant ANNUAL_INTEREST_RATE = 5; // 5% per year
    uint256 public constant LOAN_DURATION_YEARS = 20;

    uint256 public totalSupply;

    struct Payment {
        uint256 timestamp;
        uint256 amount;
        string remark;

    struct Loan {
        uint256 principal;
        uint256 interest;
        uint256 duration;
        uint256 remainingBalance;
        uint256 startDate;
        bool isClosed;

    mapping(uint256 => Payment[]) public loanPayments;
    mapping(uint256 => Loan) public loans;

    event PaymentReceived(uint256 tokenId, uint256 timestamp, uint256 amount, string remark);

    constructor() ERC721("LoanNFT", "LNFT") {}

    function initiateLoan(uint256 tokenId) private {

        loans[tokenId] = Loan({
            principal: PRICE_PER_NFT,
            interest: 0,
            duration: LOAN_DURATION_YEARS,
            remainingBalance: PRICE_PER_NFT,
            startDate: block.timestamp,
            isClosed: false

    function mint(uint256 numberOfTokens, string memory remark) external payable {
        require(totalSupply + numberOfTokens <= MAX_SUPPLY, "Exceeds max supply");
        require(msg.value == PRICE_PER_NFT * numberOfTokens, "Incorrect payment amount");

        for (uint256 i = 0; i < numberOfTokens; i++) {
            uint256 tokenId = totalSupply + 1;
            _mint(msg.sender, tokenId);

            // Record payment
            loanPayments[tokenId].push(Payment(block.timestamp, PRICE_PER_NFT, remark));

            // make Loan record

    function getLoanDetails(uint256 tokenId) external view returns (Loan memory) {
        return loans[tokenId];
    // ... (rest of the contract remains unchanged)

    function getLoanBalance(uint256 tokenId) external view returns (uint256) {
        return calculateRemainingLoan(tokenId);

    function makePayment(uint256 tokenId, string memory remark) external payable {
        require(ownerOf(tokenId) == msg.sender, "Not the owner of the NFT");
        uint256 remainingLoan = calculateRemainingLoan(tokenId);
        require(msg.value <= remainingLoan, "Payment exceeds remaining loan");
        // Update loan details
        updateLoan(tokenId, msg.value);
        // Record payment
        loanPayments[tokenId].push(Payment(block.timestamp, msg.value, remark));

        // Emit event for payment
        emit PaymentReceived(tokenId, block.timestamp, msg.value, remark);

    function updateLoan(uint256 tokenId, uint256 payment) internal {
        Loan storage loan = loans[tokenId];
        require(!loan.isClosed, "Loan is already closed");

        uint256 elapsedYears = (block.timestamp - loan.startDate) / (365 days);
        uint256 interest = loan.remainingBalance * ANNUAL_INTEREST_RATE * elapsedYears / 100;

        loan.interest = interest;
        loan.remainingBalance = loan.remainingBalance - payment;
        if (loan.remainingBalance == 0) {
            loan.isClosed = true;

    function calculateRemainingLoan(uint256 tokenId) public view returns (uint256) {
        uint256 totalPayments = 0;
        Payment[] storage payments = loanPayments[tokenId];
        Loan storage loan = loans[tokenId];

        for (uint256 i = 0; i < payments.length; i++) {
            totalPayments = totalPayments.add(payments[i].amount);

        uint256 elapsedYears = (block.timestamp.sub(loan.startDate)).div(365 days);
        uint256 interest = loan.remainingBalance.mul(ANNUAL_INTEREST_RATE).mul(elapsedYears).div(100);

        uint256 remainingLoan = loan.principal.add(interest).sub(totalPayments);

        return remainingLoan;

    function withdraw() external onlyOwner {

    // Add a function to listen to all payments on one particular LoanNFT
    function listenToPayments(uint256 tokenId) external view returns (Payment[] memory) {
        return loanPayments[tokenId];


Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top