# NFTx闪电贷 **Published by:** [halo92.eth](https://paragraph.com/@halo92/) **Published on:** 2022-03-23 **URL:** https://paragraph.com/@halo92/nftx ## Content NFTx 基本概念 功能:将NFT存入Vault铸造为vToken(1个NFT可换少于1个vToken),vToken可在sushiswap上交易。 铸造:NFT-> vToken 赎回:vToken -> NFT 闪电贷服务:任意铸造vToken。 闪电贷目前免费 从NFT铸造vToken费用(mintFee)为0.1 从vToken赎回NFT的的费用分为两种: -随机赎回NFT,费用为randomRedeemFee 0.04; -指定NFT tokenID赎回,费用为targetRedeemFee 0.06 mintFee:NFT->vToken redeemFee:vToken->NFT 手续费计算: 凭空借随机1个NFT的费用为mintFee + randomRedeemFee= 0.14个vToken。vToken在sushi中可获取价格。 核心合约 NFTXVault:本合约存放了NFT,同时该合约为ERC20,即vToken。该合约提供闪电贷服务,提供铸造vToken和赎回NFT的功能。Vault需要通过代理访问,代理地址可通过NFTXVaultFactory.vaultsForAsset(nftAddress)得到。 NFTXVaultFactory:负责创造各种NFTVault。通过该合约可以查询到NFTXVault的地址。 NFTXVaultFactory:0xBE86f647b167567525cCAAfcd6f881F1Ee558216 例子:$Ape套利 tx: https://etherscan.io/tx/0xeb8c3bebed11e2e4fcd30cbfc2fb3c55c4ca166003c7f7d319e78eaab9747098 闪电借5.2 个vToken 通过借到的5.2个vToke赎回5个BAYC,这里费用为randomRedeemFee为0.04,所以 5* 0.04 = 0.2个vToken交给FeeDistributor进行分配。到此时,0x77有6个BAYC。 用6个BAYC领取$APE空投。 将6个BAYC铸造为vToken,这里因为有mintFee 0.1,所以0.6个vToken再次被分配。最终得到5.4个vToken。 将5.2个vToken归还闪电贷,0.2个vToken在sushi上卖出得到14个eth。 套利: 投入:1 BAYC 100eth 产出:14eth 60000 个$APE(213 eth)利润: 227eth - 100eth = 127 eth 无BAYC操作套利: 套利3个:61 eth -> 30000 套利1个:14eth -> 10000 (35 eth) 手续费计算脚本 const sushiRouterV2Addr = '0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'; const weth_addr = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; const vaultFactoryAddr = '0xBE86f647b167567525cCAAfcd6f881F1Ee558216'; const getFeeByNFTAddress = async (nftaddr) => { // console.log(`get `) let provider = ethers.getDefaultProvider(); let nft = new ethers.Contract(nftaddr,erc721_abi,provider); let factory = new ethers.Contract(vaultFactoryAddr,vault_factory_abi,provider); let vaultAddrList = await factory.vaultsForAsset(nftaddr); if (vaultAddrList.length > 1) { console.log(`more vaults found`); } else if (vaultAddrList.length == 0) { console.log('no vaults'); return; } let vaultAddr = vaultAddrList[0]; let vault = new ethers.Contract(vaultAddr,vault_abi,provider); const redeemFee = await vault.randomRedeemFee(); const mintFee = await vault.mintFee() const totalFee = redeemFee.add(mintFee); const router = new ethers.Contract(sushiRouterV2Addr,router_abi,provider); // const amountOutMin; let [amountIn, _] = await router.getAmountsIn(totalFee,[weth_addr,vaultAddr]); console.log(`NFTx flashloan: borrow 1 * ${await nft.name()} NFT need fee ${ethers.utils.formatEther(amountIn)} e`); return amountIn; } async function main() { await getFeeByNFTAddress('0x7Bd29408f11D2bFC23c34f18275bBf23bB716Bc7'); } NFTx闪电贷使用方法 //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol"; import "./interface/INFTXVaultFactory.sol"; import "./interface/INFTXVault.sol"; import "./interface/IERC3156Upgradeable.sol"; import "./IClaim.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/interfaces/IERC721Receiver.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "hardhat/console.sol"; // IERC3156FlashBorrower 保证可使用闪电贷 // IERC721Receiver 是因为要将借到的vToken赎回为NFT,所以需要可以接收NFT contract NFTxFlashLoanExample is IERC3156FlashBorrower ,IERC721Receiver{ bytes32 constant private RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan"); INFTXVaultFactory public factory; struct FlashLoanParams { address nft; // NFT合约地址 uint256 amount; // num of nft address claim; // 领取空投的合约地址 uint256[] ids; // 可指定从金库Vault赎回的NFT } struct FlashLoanCallbackParams { address nft; address claim; address vault; uint256 amount; // 赎回几个nft uint256[] ids; } constructor(address _factory) { factory = INFTXVaultFactory(_factory); } function initFlashLoan(FlashLoanParams memory params) external { // factory.vaultsForAsset(nft); require(!factory.isLocked(4), "flashloan locked"); // 保证NFTx未关闭闪电贷 address[] memory addrs = factory.vaultsForAsset(params.nft); // 查找对应NFT的zVault address token = addrs[0]; IERC3156FlashLenderUpgradeable lender = IERC3156FlashLenderUpgradeable(token); lender.flashLoan(IERC3156FlashBorrowerUpgradeable(address(this)), token, // token即为NFTVault vToken params.amount * 10 ** 18, // 一个nft需要1个vtoken abi.encode(FlashLoanCallbackParams({ nft:params.nft, claim:params.claim, amount:params.amount, vault:token, ids:params.ids // 不指定id的话手续费会低 }))); } function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes memory data) external override returns (bytes32) { console.log("onFlashLoan called"); FlashLoanCallbackParams memory params = abi.decode(data,(FlashLoanCallbackParams)); INFTXVault vault = INFTXVault(params.vault); uint256[] memory ids = vault.redeem(params.amount, params.ids); // 从vToken 换为NFT,返回得到的tokenId uint256[] memory amounts; for (uint i = 0; i < ids.length; i++) { IClaim(params.claim).claimTokens(); // 领取$APE空投 IERC721(params.nft).approve(address(vault), ids[i]); // 授权Vault可将NFT从this转移到Vault } vault.mint(ids, amounts); // 铸造vToken,转移NFT到Valut IERC20(params.vault).approve(msg.sender, amount + fee); //授权保证闪电贷结束后可以_burn销毁,即归还vToken return RETURN_VALUE; // 返回该值才可成功进行闪电贷 } function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external override returns (bytes4){ console.log(string(abi.encodePacked('received ',tokenId, ' from ', from))); return IERC721Receiver.onERC721Received.selector; } } ## Publication Information - [halo92.eth](https://paragraph.com/@halo92/): Publication homepage - [All Posts](https://paragraph.com/@halo92/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@halo92): Subscribe to updates