This ERC proposes a mechanism where a person (referred to as the “Asset Owner”) can collateralize NFTs that represent locked deposits or assets, to borrow funds against them. These NFTs represent the right to claim the underlying assets, along with any accrued benefits, after a predefined maturity period. 1
Motivation
The rapidly evolving landscape of DeFi has introduced various mechanisms for asset locking, offering benefits like interest and voting rights. However, one of the significant challenges in this space is maintaining liquidity while these assets are locked. This ERC addresses this challenge by proposing a method to generate profit from locked assets using ERC-721 and ERC-4907.
In DeFi services, running Automated Market Maker (AMM), liquidity providers contribute assets to pools and receive NFTs representing their stake. These NFTs denote the rights to the assets and the associated benefits, but they also lock the assets in the pool, often causing liquidity challenges for the providers. The current practice requires providers to withdraw their assets for urgent liquidity needs, adversely affecting the pool’s liquidity and potentially increasing slippage during asset swaps.
Our proposal allows these NFTs, representing locked assets in liquidity pools, to be used as collateral. This approach enables liquidity providers to gain temporary liquidity without withdrawing their assets, maintaining the pool’s liquidity levels. Furthermore, it extends to a broader range of DeFi services, including lending and trading, where asset locking is prevalent. By allowing the collateralization of locked asset representations through NFTs, our approach aims to provide versatile liquidity solutions across DeFi services, benefitting a diverse user base within the ecosystem.
The concept of perpetual contract NFTs, which we introduce, exploits the idea of perpetual futures contracts in the cryptocurrency derivatives market. These NFTs represent the rights to the perpetual contract and its collateral, enabling them to be used effectively as collateral for DeFi composability. The perpetual contract NFT offers a new form of NFT that enhances the utility of locked assets, providing a significant advantage in DeFi applications by offering liquidity while retaining the benefits of asset locking.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Contract Interface
Solidity interface.
interfaceIPerpetualContractNFT{// Emitted when an NFT is collateralized for obtaining a loan
eventCollateralized(uint256indexedtokenId,addressindexedowner,uint256loanAmount,uint256interestRate,uint256loanDuration);// Emitted when a loan secured by an NFT is fully repaid, releasing the NFT from collateral
eventLoanRepaid(uint256indexedtokenId,addressindexedowner);// Emitted when a loan defaults, resulting in the transfer of the NFT to the lender
eventDefaulted(uint256indexedtokenId,addressindexedlender);// Enables an NFT owner to collateralize their NFT in exchange for a loan
// @param tokenId The NFT to be used as collateral
// @param loanAmount The amount of funds to be borrowed
// @param interestRate The interest rate for the loan
// @param loanDuration The duration of the loan
functioncollateralize(uint256tokenId,uint256loanAmount,uint256interestRate,uint64loanDuration)external;// Enables a borrower to repay their loan and regain ownership of the collateralized NFT
// @param tokenId The NFT that was used as collateral
// @param repayAmount The amount of funds to be repaid
functionrepayLoan(uint256tokenId,uint256repayAmount)external;// Allows querying the loan terms for a given NFT
// @param tokenId The NFT used as collateral
// @return loanAmount The amount of funds borrowed
// @return interestRate The interest rate for the loan
// @return loanDuration The duration of the loan
// @return loanDueDate The due date for the loan repayment
functiongetLoanTerms(uint256tokenId)externalviewreturns(uint256loanAmount,uint256interestRate,uint256loanDuration,uint256loanDueDate);// Allows querying the current owner of the NFT
// @param tokenId The NFT in question
// @return The address of the current owner
functioncurrentOwner(uint256tokenId)externalviewreturns(address);// View the total amount required to repay the loan for a given NFT
// @param tokenId The NFT used as collateral
// @return The total amount required to repay the loan, including interest
functionviewRepayAmount(uint256tokenId)externalviewreturns(uint256);}
Event Collateralized
The Collateralized event MUST be emitted when the collateralize function is successfully executed.
Usage: Logs the event of an NFT being used as collateral for a loan, capturing essential details like the loan amount, interest rate, and loan duration.
Event LoanRepaid
The LoanRepaid event MUST be emitted when the repayLoan function is successfully executed.
Usage: Logs the event of a loan being repaid and the corresponding NFT being released from collateral.
Event Defaulted
The Defaulted event MUST be emitted in scenarios where the loan defaults and the NFT is transferred to the lender.
Usage: Used to log the event of a loan default and the transfer of the NFT to the lender.
Function collateralize
The collateralize event SHOULD be implemented as external.
Usage: Allows an NFT owner to collateralize their NFT to receive a loan.
Function repayLoan
The repayLoan function SHOULD be implemented as external.
Usage: Enables an NFT owner to repay their loan and reclaim their NFT.
Function getLoanTerms
The getLoanTerms function MAY be implemented as externalview.
Usage: Allows querying the loan terms for a given NFT.
Function currentOwner
The currentOwner function MAY be implemented as externalview.
Usage: Enables querying the current owner of a specific NFT.
Function viewRepayAmount
The viewRepayAmount function MAY be implemented as externalview.
Usage: Enables querying the current repay amount of a specific NFT.
Rationale
Design Motivation
The design of this standard is driven by the need to address specific challenges in the DeFi sector, particularly concerning the liquidity and management of assets locked as collateral. Traditional mechanisms in DeFi often require asset holders to lock up their assets for participation in activities such as lending, staking, or yield farming, which results in a loss of liquidity. This standard aims to introduce a more flexible approach, allowing asset holders to retain some liquidity while their assets are locked, thereby enhancing the utility and appeal of DeFi products.
Design Decision
Dual-Role System (Asset Owner and DeFi Platform/Contract): A clear division is established between the NFT owner (asset holder) and the DeFi platform or contract utilizing the NFT as collateral. This distinction simplifies the management of rights and responsibilities, enhancing clarity and reducing potential conflicts.
Enhancing Liquidity without Compromising Asset Locking Benefits: A key feature of this standard is enabling asset owners to use their NFTs, which represent locked assets, as collateral to secure loans. This approach allows asset owners to access liquidity without needing to withdraw their assets from pools or staking programs, thus preserving the associated benefits like interest accrual or voting rights.
Automated Loan and Collateral Management: The integration of automated features for managing the terms and conditions of the collateralized NFT is a deliberate choice to minimize transaction costs and complexity.
DeFi Composability: The strategic emphasis on DeFi composability, particularly the integration between asset-locking and collateralizing services, is pivotal for this standard. This approach aims to streamline the adoption of the standard across diverse DeFi platforms and services, fostering seamless connections within the DeFi ecosystem.
Alternate Designs and Related Work
Comparison with ERC-4907: While ERC-4907 also introduces a dual-role model for NFTs (owner and user), our standard focuses specifically on the use of NFTs for collateralization in financial transactions, diverging from ERC-4907’s rental-oriented approach.
Improvement Over Traditional Collateralization Methods: Compared to traditional DeFi collateralization, which often requires complete asset lock-up, this standard proposes a more dynamic and flexible model that allows for continued liquidity access.
Backwards Compatibility
Fully compatible with ERC-721 and integrates with ERC-4907 for renting NFTs.
import{expect}from"chai";import{ethers}from"hardhat";describe("PerpetualContractNFTDemo",function(){it("should allow an owner to collateralize an NFT, rent it to a contract, and then have the owner repay the loan",asyncfunction(){const[owner]=awaitethers.getSigners();constPerpetualContractNFTDemo=awaitethers.getContractFactory("PerpetualContractNFTDemo");constdemo=awaitPerpetualContractNFTDemo.deploy("DemoNFT","DNFT");awaitdemo.waitForDeployment();expect(demo.target).to.be.properAddress;// Mint an NFT to the owner
awaitdemo.mint(1,owner.address);// Owner collateralizes the NFT for a loan
constloanAmount=ethers.parseUnits("1","ether");// 1 Ether in Wei. Use Wei to avoid precision error.
constinterest=5;// 5% interest
constexpiration=Math.floor(newDate().getTime()/1000)+3600;// Expire after 60 minutes (3600 seconds), convert it to seconds because `hours` in solidity converted to seconds
awaitdemo.connect(owner).collateralize(1,loanAmount,interest,expiration);// tokenId, loanAmount, interestRate, loanDuration
// Check current user of the NFT (should be the contract address)
expect(awaitdemo.userOf(1)).to.equal(demo.target);// Borrower repays the loan to release the NFT
constrepayAmountWei=awaitdemo.connect(owner).viewRepayAmount(1);awaitdemo.connect(owner).repayLoan(1,repayAmountWei);// Check if the NFT is returned to the original owner after the loan is repaid
expect(awaitdemo.userOf(1)).to.equal("0x0000000000000000000000000000000000000000");});});
Run in Terminal:
npx hardhat test
Reference Implementation
// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.0;//import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import"./IPerpetualContractNFT.sol";import"./ERC4907/ERC4907.sol";contractPerpetualContractNFTisERC4907,IPerpetualContractNFT{structLoanInfo{addressborrower;// Address that borrowed against the NFT
uint256loanAmount;// Amount of funds borrowed
uint256interestRate;// Interest rate for the loan
uint64loanDuration;// Duration of the loan
uint256loanStartTime;// Timestamp when the loan starts
}mapping(uint256=>LoanInfo)internal_loans;//Constructor to initialize the Perpetual Contract NFT contract with the given name and symbo
constructor(stringmemoryname_,stringmemorysymbol_)ERC4907(name_,symbol_){}functioncollateralize(uint256tokenId,uint256loanAmount,uint256interestRate,uint64loanDuration)publicoverride{require(ownerOf(tokenId)==msg.sender||isApprovedForAll(ownerOf(tokenId),msg.sender)||getApproved(tokenId)==msg.sender,"Not owner nor approved");LoanInfostorageinfo=_loans[tokenId];info.borrower=msg.sender;// The loan amount should reflect the asset's value as represented by the NFT, considering an appropriate loan-to-value (LTV) ratio.
info.loanAmount=loanAmount;info.interestRate=interestRate;info.loanDuration=loanDuration;info.loanStartTime=block.timestamp;setUser(tokenId,address(this),loanDuration);emitCollateralized(tokenId,msg.sender,loanAmount,interestRate,loanDuration);// Further logic can be implemented here to manage the lending of assets
}functionrepayLoan(uint256tokenId,uint256repayAmount)publicoverride{require(_loans[tokenId].borrower==msg.sender,"Not the borrower.");// Calculate the total amount due for repayment
uint256totalDue=viewRepayAmount(tokenId);// Check if the repayAmount is sufficient to cover at least a part of the total due amount
require(repayAmount<=totalDue,"Repay amount exceeds total due.");// Calculate the remaining loan amount after repayment
_loans[tokenId].loanAmount=totalDue-repayAmount;// Resets the user of the NFT to the default state if the entire loan amount is fully repaid
if(_loans[tokenId].loanAmount==0){setUser(tokenId,address(0),0);}emitLoanRepaid(tokenId,msg.sender);}functiongetLoanTerms(uint256tokenId)publicviewoverridereturns(uint256,uint256,uint256,uint256){LoanInfostorageinfo=_loans[tokenId];return(info.loanAmount,info.interestRate,info.loanDuration,info.loanStartTime);}functioncurrentOwner(uint256tokenId)publicviewoverridereturns(address){returnownerOf(tokenId);}functionviewRepayAmount(uint256tokenId)publicviewreturns(uint256){if(_loans[tokenId].loanAmount==0){// If the loan amount is zero, there is nothing to repay
return0;}// The interest is calculated on an hourly basis, prorated based on the actual duration for which the loan was held.
// If the borrower repays before the loan duration ends, they are charged interest only for the time the loan was held.
// For example, if the annual interest rate is 5% and the borrower repays in half the loan term, they pay only 2.5% interest.
uint256elapsed=block.timestamp>(_loans[tokenId].loanStartTime+_loans[tokenId].loanDuration)?_loans[tokenId].loanDuration/1hours:(block.timestamp-_loans[tokenId].loanStartTime)/1hours;// Round up
// Example: 15/4 = 3.75
// round((15 + 4 - 1)/4) = 4, round((15/4) = 3)
uint256interest=((_loans[tokenId].loanAmount*_loans[tokenId].interestRate/100)*elapsed+(_loans[tokenId].loanDuration/1hours)-1)/(_loans[tokenId].loanDuration/1hours);// Calculate the total amount due
uint256totalDue=_loans[tokenId].loanAmount+interest;returntotalDue;}// Additional functions and logic to handle loan defaults, transfers, and other aspects of the NFT lifecycle
}