# NFTx闪电贷

By [halo92.eth](https://paragraph.com/@halo92) · 2022-03-23

---

NFTx 基本概念
---------

功能：将NFT存入Vault铸造为vToken（1个NFT可换少于1个vToken），vToken可在sushiswap上交易。

铸造：NFT-> vToken 赎回：vToken -> NFT

闪电贷服务：任意铸造vToken。

1.  闪电贷目前免费
    
2.  从NFT铸造vToken费用（mintFee）为0.1
    
3.  从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](https://etherscan.io/tx/0xeb8c3bebed11e2e4fcd30cbfc2fb3c55c4ca166003c7f7d319e78eaab9747098)

1.  闪电借5.2 个vToken
    
2.  通过借到的5.2个vToke赎回5个BAYC，这里费用为randomRedeemFee为0.04，所以 5\* 0.04 = 0.2个vToken交给FeeDistributor进行分配。到此时，0x77有6个BAYC。
    
3.  用6个BAYC领取$APE空投。
    
4.  将6个BAYC铸造为vToken，这里因为有mintFee 0.1，所以0.6个vToken再次被分配。最终得到5.4个vToken。
    
5.  将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;
        }
    }

---

*Originally published on [halo92.eth](https://paragraph.com/@halo92/nftx)*
