Collateral-Free Integration Guide

The official instructions on how to interact with our open Collateral-Free rental contracts.

This tutorial was written for SDK version 6.0.0.

Lending and Renting NFTs

To interact with our smart contracts, we always recommend using our SDK. We view the SDK as the source of truth for all of our deployments and business logic; most importantly, it versions where our smart contracts reside in the metaverse and defines objectively how they work.

To work with collateral-free lendings, we'd use the Sylvester contract:

import {
  getRenftContract,
  DEPLOYMENT_SYLVESTER_ETHEREUM_MAINNET_V0,
  SylvesterV0FunctionInterface,
} from "@renft/sdk";

import { ethers } from "ethers";

const signer = ethers.Wallet.createRandom(); // In practical dApps, this would be the user's wallet!

const renft: SylvesterV0FunctionInterface = getRenftContract({
  deployment: DEPLOYMENT_SYLVESTER_ETHEREUM_MAINNET_V0,
  signer,
});

💭 We name our contracts after famous cats! You can find the name mappings here. Meow!

Batching Transactions

Any of the following functions support batching. This is shown below for the lend() instruction, where the user lends an AstroCat and a CatPlsr within the same function call:

import { NFTStandard, PaymentToken, packPrice } from "@renft/sdk";

const astroCatLendingArgs = [
  NFTStandard.E1155,
  "0x0db8c099b426677f575d512874d45a767e9acc3c",
  "1",
  1,
  1,
  packPrice("1"),
  PaymentToken.WETH,
];

const catPlsrLendingArgs = [
  NFTStandard.E1155,
  "0x0db8c099b426677f575d512874d45a767e9acc3c",
  "2",
  1,
  1,
  packPrice("0.5"),
  PaymentToken.WETH,
];

const lendingArgs = astroCatLendingArgs.map((value, index) => 
  [value, catPlsrLendingArgs[index]]
);

await renft.lend(...lendingArgs);

Lending

dApps invoke lend() when they wish to create a new on-chain lending. A lending is when a market maker asserts that they wish to allow other users to temporarily have rights to the asset for the duration of the rental.

Since the lend() function is designed to batch multiple NFTs together, you can create a single-element lending by using single-element arrays for each of the required parameters:

import { NFTStandard, PaymentToken, packPrice } from "@renft/sdk";

const nftStandard = NFTStandard.E1155;
const nftAddress = "0x0db8c099b426677f575d512874d45a767e9acc3c";
const tokenID = "1";
const lendAmount = 1; // Quantity of the NFT to lend.
const maxRentDuration = 1; // Duration is measured in days.
const dailyRentPrice = packPrice("1"); // Create a properly formatted rental price.
const paymentToken = PaymentToken.WETH;

await renft.lend(
  [nftStandard],
  [nftAddress],
  [tokenID],
  [lendAmount],
  [maxRentDuration],
  [dailyRentPrice],
  [paymentToken]
);

Renting

A renting is created when a user discovers a compelling lending on the blockchain and decides to take the maker up on their offer. In this instance, the user is the taker of the lending. Much like the previous example, we call the rent() function with vectorized data representing each individual token to be rented in a gas-optimized batch.

import { NFTStandard } from "@renft/sdk";

const nftStandard = NFTStandard.E1155;
const nftAddress = "0x0db8c099b426677f575d512874d45a767e9acc3c";
const tokenID = "1";
const lendingID = "1"; // this information is pulled from the subgraph
const rentDuration = 1; // in days
const rentAmount = 1;

await renft.rent(
  [nftStandard],
  [nftAddress],
  [tokenID],
  [lendingID],
  [rentDuration],
  [rentAmount]
);

Stopping a Rental

For a non-collateralized rental, even though no real possession of the NFT is given (it is a virtualized rights-to-ownership), the renter must signal that they have concluded "using" the NFT with a call to stopRent().

If the renter fails to make this call, the lender is permitted to invoke claimRent(), explained in the following section, to redeem the full amount of rent.

💭 Invocations to stopLend() are disabled until the lender calls claimRent() first.

import { NFTStandard } from "@renft/sdk";

const nftStandard = NFTStandard.E1155;
const nftAddress = "0x0db8c099b426677f575d512874d45a767e9acc3c";
const tokenID = "1";
const lendingID = "1"; // from subgraph
const rentingID = "1"; // from subgraph

await renft.stopRent(
  [nftStandard],
  [nftAddress],
  [tokenID],
  [lendingID],
  [rentingID],
);

Claiming Rent

Lenders can claim rent by calling the claimRent() function.

Remember, the smart contract enforces the rules of lending and renting; you can try to claim the rent of an ongoing lending which doesn't belong to you, but the transaction will be reverted!

import { NFTStandard } from "@renft/sdk";

const nftStandard = NFTStandard.E1155;
const nftAddress = "0x0db8c099b426677f575d512874d45a767e9acc3c";
const tokenID = "1";
const lendingID = "1";
const rentingID = "1";

await renft.claimRent(
  [nftStandard],
  [nftAddress],
  [tokenID],
  [lendingID],
  [rentingID],
);

Stopping a Lending

Finally, there's the stopLend function, which is called by the lender. This prevents the provided assets from being rented out any longer, much to the chagrin of prospective renters.

import { NFTStandard } from "@renft/sdk";

const nftStandard = NFTStandard.E1155;
const nftAddress = "0x0db8c099b426677f575d512874d45a767e9acc3c";
const tokenID = "1";
const lendingID = "1";

await renft.stopLend(
  [nftStandard],
  [nftAddress],
  [tokenID],
  [lendingID],
);

To learn about more about how we determine the status of a particular lending or renting, check out this guide!

How to unpack data?

In reNFT, prices are returned in a custom format. This is a performance optimization which enables an entire lending to fit snugly inside a single storage slot, which saves gas.

To unpack them, we can use the unpackPrice() function:

import { PaymentToken, unpackPrice } from "@renft/sdk";

// Convert a low-level representation of an ERC-20 used on
// the marketplace into a TypeScript-friendly enum.
const parsePaymentToken = (tkn: string): PaymentToken => {
  switch (tkn) {
    case "0":
      return PaymentToken.SENTINEL;
    case "1":
      return PaymentToken.WETH;
    case "2":
      return PaymentToken.DAI;
    case "3":
      return PaymentToken.USDC;
    case "4":
      return PaymentToken.USDT;
    case "5":
      return PaymentToken.TUSD;
    default:
      return PaymentToken.DAI;
  }
};

// In this example, let's imagine we've read Lending collection data
// from a Sylvester subgraph. Here's how we'd transform the low-level
// blockchain data into high-level, human (and feline) friendly types.
const lendingsDataToLendings = (
  theGraphLendings: TheGraphLending[]
) => {
  const theGraphToLending = (theGraphLending: TheGraphLending) => {
    return {
      lendingID: theGraphLending.id,
      lenderAddress: theGraphLending.lenderAddress,
      // Convert the price back into a BigNumber.
      dailyRentPrice: unpackPrice(theGraphLending.dailyRentPrice),
      maxRentDuration: Number(theGraphLending.maxRentDuration),
      lendAmount: Number(theGraphLending.lendAmount),
      paymentToken: parsePaymentToken(theGraphLending.paymentToken),
      lentAt: Number(theGraphLending.lentAt),
    };
  };

  return theGraphLendings.map(theGraphToLending);
};

Last updated