Skip to main content

Contract Integration Guide

A guide for how to integrate with FluidNFT at the contract level.

Modules

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: Incentivized ERC-20 tokens that represent assets
  • debtTokens: Incentivized ERC-20 tokens that represent liabilities
  • stakedTokens: Incentivized ERC-20 tokens that represent staked assets

Execution

As it may be benefitial 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.

P2Pool

Core peer-to-pool operations.

Deposit and withdraw

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);

Borrow and repay

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);

Liquidate

If a loan is no longer sufficiently overcollateralized, i.e. when the amount of outstanding debt exceeds the maximum loan-to-value, it can be liquidated.

The price is determined via a prioprietary dynamic-Dutch auction mechanic that results in a high liquidation price when the loan is a little undercollateralized, that decays exponentially as the loan becomes more and more undercollateralized.

The liquidation price is further dependent on the asset being used to purchase the underlying collateral, with smaller and larger discounts available for ther reserve's fTokens and stakedToken, respecitively.

// 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);

P2Peer

Core peer-to-peer operations.

List asset & asset-bundle

TODO

Make offer, trait-offer, & collection-offer

TODO

Take listing, & transfer promissory note

TODO

Take offer, & transfer obligation receipt

TODO

Refinance, renegotiate, and repay loan

TODO

Foreclose loan

TODO