The FluidNFT protocol is a collection of smart contracts connected via a modular system. Each module handles a specific aspect of the protocol. Depending on your use case you may need to interact with several different contract addresses.
Some modules are global, for example:
ExecutionDelegate: Grant ERC20, ERC721 & ERC1155 token access across the protocol
ExecutionManager: Batch requests to save on gas
LendingPool: Execute peer-to-pool transactions
Lending: Execute peer-to-peer transactions
Some modules are asset-specific, for example:
fTokens: Incentivised ERC-20 tokens that represent assets
stableDebtTokens: Incentivised ERC-20 tokens that represent stable interest rate liabilities
As it may be beneficial to batch requests to save on gas all modules can be called using the optional ExecutionManager.
Example of depositing into a lending pool with the execution manager:
// Use the lending pool module:
ILendingPool lendingPool = ILendingPool(LENDING_POOL);
// Deposit asset tokens into a pool
lendingPool.deposit(amount, reserveId, onBehalfOf, referralCode);
Without the execution manager:
// Use the execution manager module
IExecutionManager executionManager = IExecutionManager(EXECUTION_MANAGER);
// Instantiate the lending pool module to access the selector:
ILendingPool lendingPool = ILendingPool(LENDING_POOL);
// Deposit asset tokens into a pool
executionManager.call(
LENDING_POOL,
abi.encodeWithSelector(
lendingPool.deposit.selector,
amount,
reserveId,
onBehalfOf,
referralCode
)
);
Note the ExecutionManager supports both .call() and .multiCall() functionality, for batch execution.
The remainder of this document details how to integrate without the execution module, for the sake of brevity.
In order to earn passive yield, you need to deposit into a lending pool.
// Approve the execution delelgate contract to pull your tokens:
IERC20(asset).approve(EXECUTION_DELEGATE, type(uint).max);
// Use the lending pool module:
ILendingPool lendingPool = ILendingPool(LENDING_POOL);
// Get the default reserve id for a given collateral-asset pair
// "collateral" is the NFT contract address, e.g. 0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7 for Meebits
// "asset" is the asset contract address, e.g. 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 for WETH (Wrapped Ether)
// "maxTokenId" is the maxTokenId for the collection, e.g. 19999 for Meebits
// "minTokenId" is the minTokenId for the collection, e.g. 0 for Meebits
lendingPool.getReserveId(collateral, asset, maxTokenId, minTokenId);
// Deposit asset tokens into the pool
// "amount" the amount of asset tokens, e.g. 12.5e18 (assuming 18 decimal places)
// "reserveId" the id of the reserve, e.g. "1" (returned in previous call)
// "onBehalfOf" enables the deposit to be initiated from a seperate account, or use address(this) for the same account
// "referralCode" is used for fee sharing with our partners, or 0 for none
lendingPool.deposit(amount, reserveId, onBehalfOf, referralCode);
Use the fToken reserve to check balances.
// Retrieve fToken address
lendingPool.getConfiguration(reserveId).fTokenAddress;
// Use the fToken
IFToken fToken = IFToken(fTokenAddress);
// Retreive the user's balance + interest
// Grows each block, assuming there are borrowers to generate yield
fToken.balanceOf(address(this));
// Retreive the user's deposit amount
// Internal book-keeping amount that doesn't change in time
fToken.scaledBalanceOf(address(this));
You can withdraw at anytime to retrieve your funds.
// Withdraw asset tokens from the pool
// "amount" the amount of asset tokens, e.g. type(uint256).max to retrieve all deposits plus interest
// "reserveId" the id of the reserve, e.g. "1"
// "to" enables withdrawing to seperate account, or use address(this) for the same account
lendingPool.withdraw(amount, reserveId, to);
To borrow an asset, you must supply collateral to be held in escrow for the duration of the loan.
// Approve the execution delelgate contract to pull your tokens:
IERC721(collateral).setApprovalForAll(EXECUTION_DELEGATE);
// Use the lending pool module:
ILendingPool lendingPool = ILendingPool(LENDING_POOL);
// Get the default reserve id for a given collateral-asset pair
// "collateral" is the NFT contract address, e.g. 0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7 for Meebits
// "asset" is the asset contract address, e.g. 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 for WETH (Wrapped Ether)
// "maxTokenId" is the maxTokenId for the collection, e.g. 19999 for Meebits
// "minTokenId" is the minTokenId for the collection, e.g. 0 for Meebits
lendingPool.getReserveId(collateral, asset, maxTokenId, minTokenId);
// Borrow asset tokens from the pool
// "amount" the amount of asset tokens, e.g. 12.5e18 (assuming 18 decimal places)
// "tokenId" the id of the token to be used as collateral, e.g. "1" (a tokenId held in your wallet)
// "reserveId" the id of the reserve, e.g. "1" (returned in previous call)
// "onBehalfOf" enables the borrow to be initiated from a seperate account, or use address(this) for the same account
// "referralCode" is used for fee sharing with our partners, or 0 for none
lendingPool.borrow(amount, reserveId, onBehalfOf, referralCode);
Use the debtToken reserve to check debt balances.
// Retrieve debtToken address
lendingPool.getConfiguration(reserveId).debtTokenAddress;
// Use the debtToken
IDebtToken debtToken = IDebtToken(debtTokenAddress);
// Retreive the user's debt balance + interest
// Grows each block
debtToken.balanceOf(address(this));
// Retreive the user's borrow amount
// Internal book-keeping amount that doesn't change in time
debtToken.scaledBalanceOf(address(this));
You can repay at anytime to retrieve your collateral.
// Repay asset tokens to the pool
// "collateral" is the NFT contract address, e.g. 0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7 for Meebits
// "tokenId" the id of the token used as collateral, e.g. "1"
// "amount" the amount of asset tokens, e.g. type(uint256).max to repay the borrow amount plus interest
lendingPool.repay(collateral, tokenId, amount);
If a loan is no longer sufficiently overcollateralised, i.e. when the amount of outstanding debt exceeds the maximum loan-to-value, it can be liquidated.
The price is determined via a proprietary dynamic-Dutch auction mechanic that results in a high liquidation price when the loan is a little undercollateralised, that decays exponentially as the loan becomes more and more undercollateralised.
The liquidation price is further dependent on the asset being used to purchase the underlying collateral, with smaller and larger discounts available for their reserve's fTokens and stakedToken, respectively.
// Approve the execution delelgate contract to pull your tokens:
IERC20(asset).approve(EXECUTION_DELEGATE, type(uint).max);
// Use the lending pool module:
ILendingPool lendingPool = ILendingPool(LENDING_POOL);
// Liquidate an undercollateralized loan to purchase the underlying collateral at a discount
// "collateral" is the NFT contract address, e.g. 0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7 for Meebits
// "tokenId" the id of the token used as collateral, e.g. "1"
// "paymentAsset" is the contract address of the amount paid to liquidate, e.g. 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 for WETH
// "paymentAmount" the amount of asset token paid to liquidate the loans, e.g. 12.5e18 (assuming 18 decimal places)
// "onBehalfOf" enables the deposit to be initiated from a seperate account, or use address(this) for the same account
lendingPool.liquidate(collateral, tokenId, paymentAsset, paymentAmount, onBehalfOf);