Mint Financing
Creators can offer financing on a lazy mint of their collection or after an NFT has been minted during a drop. Creators can sell more NFTs, more quickly, for more money. Buyers have the ability to buy more NFTs with the same amount of money. Buyers can sell the NFTs they have minted at any time if the proceeds of the sale cover the remaining principal and interest of the loan.
1. Deploy ERC721MintFinancing.sol or update mint contract
First, you will need to enable your users to offer financing on the mint of their NFTs at the contract level.
NiftyApes has created an extension for ERC721, ERC721MintFinancing.sol, that you can easily deploy on behalf of your users. It is an extension of a full ERC721 Implementation with metadata. It does not support enumerability.
Alternatively, you can update your own mint contract with the relevant functions and state variables found in ERC721MintFinancing.sol.
Your contract may look differently, but to integrate NiftyApes into your mint contract you will need to add some version of the following solidity:
//SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "../interfaces/sellerFinancing/ISellerFinancing.sol";
/// @title ERC721MintFinancing
/// @custom:version 1.0
/// @author captnseagraves (captnseagraves.eth)
contract ERC721MintFinancing is ERC721, Ownable, ReentrancyGuard {
using Counters for Counters.Counter;
/// @dev Token ID Tracker
Counters.Counter private _tokenIdTracker;
/// @dev The stored address for the seller financing contract
address public sellerFinancingContractAddress;
error ZeroAddress();
error CannotMint0();
error CollectionOfferLimitReached();
error InsufficientMsgValue(uint256 given, uint256 expected);
error InvalidNftContractAddress(address given, address expected);
error InvalidSigner(address signer, address expected);
error ReturnValueFailed();
constructor(
string memory _name,
string memory _symbol,
address _sellerFinancingContractAddress
) ERC721(_name, _symbol) {
_requireNonZeroAddress(_sellerFinancingContractAddress);
sellerFinancingContractAddress = _sellerFinancingContractAddress;
}
/// @param newSellerFinancingContractAddress New address for SellerFinancing contract
function updateSellerFinancingContractAddress(
address newSellerFinancingContractAddress
) external onlyOwner {
_requireNonZeroAddress(newSellerFinancingContractAddress);
sellerFinancingContractAddress = newSellerFinancingContractAddress;
}
/// @notice Mints an NFT with financing
/// @dev The Mint Financing Offer must come from the owner of this contract
/// @param offer The seller financing offer made by this contract owner
/// @param signature The signed seller financing offer made by this contract owner,
/// @param count The number of NFTs requested to mint
/// @dev The count must be greater than 0.
/// If the count increments the sellerfinancing.collectionOfferLimit counter up to the collectionOffer limit
/// all NFTs will be minted up to the limit and excess funds will be returned.
/// If the first NFT of a collection is minted with finance the collection tokenIds will begin at index 1
function mintWithFinancing(
ISellerFinancing.Offer memory offer,
bytes calldata signature,
uint256 count
) external payable nonReentrant returns (uint256[] memory tokenIds) {
address signer = ISellerFinancing(sellerFinancingContractAddress).getOfferSigner(
offer,
signature
);
uint64 collectionOfferLimitCount = ISellerFinancing(sellerFinancingContractAddress)
.getCollectionOfferCount(signature);
tokenIds = new uint256[](count);
// requireSignerIsOwner
if (signer != owner()) {
revert InvalidSigner(signer, owner());
}
// requireValidNftContractAddress
if (offer.nftContractAddress != address(this)) {
revert InvalidNftContractAddress(offer.nftContractAddress, address(this));
}
// requireMsgValueGreaterThanOrEqualToOfferDownPaymentAmountTimesCount
if (msg.value < (offer.downPaymentAmount * count)) {
revert InsufficientMsgValue(msg.value, (offer.downPaymentAmount * count));
}
// requireCountIsNot0
if (count == 0) {
revert CannotMint0();
}
// requireCollectionOfferLimitNotReached
if (collectionOfferLimitCount >= offer.collectionOfferLimit) {
revert CollectionOfferLimitReached();
}
// calculate number of nfts to mint
uint256 nftsToMint = Math.min(
count,
(offer.collectionOfferLimit - collectionOfferLimitCount)
);
// loop through and mint nfts
for (uint i; i < nftsToMint; ++i) {
// increment nftid tracker
_tokenIdTracker.increment();
// mint nft
_safeMint(owner(), _tokenIdTracker.current());
// append new nftId to returned tokensIds
tokenIds[i] = _tokenIdTracker.current();
// Execute loan
ISellerFinancing(sellerFinancingContractAddress).buyWithFinancing{
value: offer.downPaymentAmount
}(offer, signature, msg.sender, _tokenIdTracker.current());
}
// if there is a greater number of NFTs requested than available return value
if (nftsToMint < count) {
(bool success, ) = address(msg.sender).call{
value: msg.value - (offer.downPaymentAmount * nftsToMint)
}("");
// require ETH is successfully sent to msg.sender
// we do not want ETH hanging in contract.
if (!success) {
revert ReturnValueFailed();
}
}
}
function _requireNonZeroAddress(address given) internal pure {
if (given == address(0)) {
revert ZeroAddress();
}
}
}
What we added to the ERC721 contract:
- A state variable referencing the core NiftyApes Seller Financing contract.
- A function to update the Seller Financing Contract Address in the event of a new protocol version release.
- A function the allows a user to mint a specified number of NFTs with a valid financing offer from the owner of the ERC721 contract.
2. Provide an option to offer financing for creators
Second, you'll need to enable creators to offer financing on their mint. This means providing them with a button or selection mechanism during collection creation like the one shown below. This can happen after a collection has been created as well.
Collection Creation Form with Mint Financing Option
3. Enable financing offer creation
Third, once the option to offer financing has been selected you'll need to enable the creator to set the terms of the financing in the UI, call setApprovalForAll on the 721 contract, and post a signed offer to the NiftyApes API. The user will fill out the Collection form and financing terms prior to any contract deployment, but the setApprovalForAll transaction call and POST call to the NiftyApes API will occur following a successful deployment of the ERC721MintFinancing contract.
- You will need to implement the useERC721SetApprovalForAll hook to approve any newly minted asset for financing in the ERC721MintFinancing contract
- And POST a signed offer to the NiftyApes API using the /offers route for the new collection. The creator has the option to specify how many NFTs they will offer financing on via the
collectionOfferLimit
variable in their offer. - Inside of a financing terms UI similar to the screens below:
Selected Mint Financing With Standard Terms
Selected Mint Financing With Expanded Form
4. Enable buyers to discover financing offers
Fourth, you'll need to enable buyers to discover the financing offers created by a seller. You may chose to implement this on your home screen, a collection section, and/or an individual asset screen.
- You will need to implement the useOffers hooks anywhere you would like to fetch offers, perhaps your home screen, a collection section, and/or an individual asset screen.
Mint Page with Financing Option
5. Enable buyers to select financing at mint
Finally, you will need to make the financing option available at the time of mint.
- You will need to implement the mintWithFinancing hook to execute a new mint and financing loan in the same transaction.
Mint Financing Option on Trending Screen
Show details of financing
Purchase confirmation and additional loan information
Updated over 1 year ago