Castle Crush (Reward Share)

This is a guide on how to the reNFT's SDK to interact with our Reward Share contracts.

This tutorial was written for SDK version 6.0.0.

The reNFT SDK is written in TypeScript. You can install the SDK by running yarn add @renft/sdk from your Command Line Interface (CLI).

In the following section, we'll talk about Whoopi; a smart contract which enables reward-sharing rental cycles for the players of Castle Crush on the Avalanche network.

Lending a Card

During a lending, the Whoopi contract takes custody of the Castle Crush NFT instead of sending it to the renter directly. This means, similarly to Sylvester, it operates using non-collateralized lending (i.e. the renter doesn't have to put up some form of deposit in case they don't feel like returning the token).

Upon creation of the lending, the NFT resides in the contract for as long as the lending is active.

⚠️ If there is no active renting, the lender may choose to terminate the lending early. However, if there is an active renting, the lender must wait for it to conclude.

Since Whoopi takes custody of the NFT, lenders are required to approve the NFT for handling by the smart contract. You can find an example of how to manage the approval process here.

Once a lender has authorized permission for the smart contract to trustlessly manage rentals, the lender can begin renting out various gnarly cards in their collection. In the following snippet, we'll walk through the flow of lending out two cards from Castle Crush:

import { JsonRpcProvider } from '@ethersproject/providers';
import { parseFixed } from '@ethersproject/bignumber';
import { Wallet } from '@ethersproject/wallet';
import {
  Whoopi,
  PaymentToken,
  RESOLVERS,
  getRenftContract,
  DEPLOYMENT_WHOOPI_AVALANCHE_MAINNET_V0,
  NETWORK_RESOLVERS,
} from '@renft/sdk';

const provider = new JsonRpcProvider('https://api.avax-test.network/ext/bc/C/rpc');
const privKey = '';

const wallet = new Wallet(privKey);
await wallet.connect(provider);

const castleCrushNftAddress = "0xeA4E79F0a40A9A468a5159499b738dc6b1332447";

const whoopi = getRenftContract({
  deployment: DEPLOYMENT_WHOOPI_AVALANCHE_MAINNET_V0,
  signer: wallet,
});

const { network: { type: networkType } } = DEPLOYMENT_WHOOPI_AVALANCHE_MAINNET_V0;

// Network Resolvers define the contract addresses that various ERC-20
// tokens have been deployed to on each network. Note that not all
// ERC-20s have been deployed to every network. For cases where a token
// is missing, it will take the value of PaymentToken.SENTINEL; an
// internal value we use to represent a missing token.
const paymentTokenResolvers = NETWORK_RESOLVERS[networkType];

// Which tokenIds we'd like to rent out from our wallet.
const tokenId = [210, 200];

// ! Note that if allowedRenters is empty, you must set upfrontRentFee to
// ! a non zero value.
const upfrontRentFee = [
  parseFixed("1", paymentTokenResolvers[PaymentToken.USDC]).toString(),
  parseFixed("1", paymentTokenResolvers[PaymentToken.USDC]).toString()
];

// ! you can't use SENTINEL as a payment token, even though
// ! you don't want to set an upfront rent fee. Just use any
// ! payment token in such a case.
const paymentToken = [PaymentToken.USDC, PaymentToken.USDC];

// Which addresses get to benefit from the terms of the rental?
const revShareBeneficiaries = [
  ["0x000000045232fe75A3C7db3e5B03B0Ab6166F425", "0x465DCa9995D6c2a81A9Be80fBCeD5a770dEE3daE"],
  ["0x465DCa9995D6c2a81A9Be80fBCeD5a770dEE3daE", "0xeA4E79F0a40A9A468a5159499b738dc6b1332447"]
];

// ! portions sum cannot be 100 here. At lend, we don't know who will rent,
// ! and the renter is always a mandatory part in rev share. We are not setting
// ! the renter here at lend time. Therefore, 100 - sum(portions) is what
// ! gets eventually assigned to the renter.
const revSharePortions = [
  [50, 40], // 10% is how much the renter will get on this lending
  [90, 5] // 5% is how much ther renter will get on this lending
];

// * means 1 and 2 cycles respectively for each token being rented.
const maxRentDuration = [1, 2];

const txn = await whoopi.lend(
  castleCrushNftAddress,
  tokenId,
  upfrontRentFee,
  revShareBeneficiaries,
  revSharePortions,
  maxRentDuration,
  paymentToken,
  undefined,
  // ! uncomment this if it does not allow you to execute because it predicts that
  // ! the transaction will fail
  // { gasLimit: 1000000 }
);

const receipt = await txn.wait();

Renting a Card

To initialize a rental, the renter must approve the PaymentToken defined in the lending to be operated by the Whoopi contract on their behalf. To enable this, check out the following example on authorizing payment tokens.

Once the renter has approved the required PaymentToken, and they possess at least the upfrontRentFee amount of the token defined in the Lending to initialize a rental, they may proceed.

In the following example, let's see how a wallet with a sufficient PaymentToken balance may initialize the rental of two tokens that have been made available by a lending:

const tokenId = [210, 200];
const lendingId = [3, 4];
const rentingDuration = [1, 2];

const txn = await whoopi.rent(
  castleCrushNftAddress,
  tokenId,
  lendingId,
  rentingDuration
  // { gasLimit: 1000000 }
);

const receipt = await txn.wait();

Stopping a Lending

A lender may only stop a lending if there is no active renting associated with the lending. This enables renters to enjoy the full terms of their rental.

It is this property of the transaction, where a lending may only be stopped after a rental is over, why it is vital to set a maxRentDuration.

A maxRentDuration places an upper-limit on the maximum number of cycles that a token can be rented out for.

For example, if maxRentDuration is set to 3, it would mean that anyone in the marketplace would be allowed to rent the lending for up to 3 consecutive cycles before the window of opportunity is over. As a lender, you do not need to approve anything for this process, since this is all managed autonomously on the smart contract.

Once the terms of the lending period is over, or the lending isn't actively being rented out, a lender is free to call the stop lending function:

const tokenId = [210, 200];
const lendingId = [3, 4];

const txn = await whoopi.stopLending(
  castleCrushNftAddress,
  tokenId,
  lendingId,
  // { gasLimit: 1000000 }
);

const receipt = await txn.wait();

How do rentings terminate?

Unlike our other protocols, stopping a renting is managed internally, automatically via the reNFT bot. 🤖

Renters don't have to do anything!

In fact, only reNFT's bot possesses the capability to terminate the rentals. It performs this operation at midnight, after the completion of each Castle Crush lending cycle. This saves on costly expensive transactions for everyone.

The bot analyzes all of the rentings once-an-hour, meaning that it is possible that it will stop the renting with up to, but not exceeding, a one hour delay. This means sometimes you'll need to be a little patient.

Paying Rewards

Similarly to renting, a lender must ensure that the Castle Crush contract is approved to spend their ERC-20 reward token.

When paying rewards, a lender will transfer a lump sum per lending (along with the renters' addresses) and the smart contract will calculate handle the reward splitting as per the conditions of the original lending. This helps reward revenue share parties, and enforces the correct distribution of value to the pre-agreed shares on a given lending. Simple!

const tokenId = [210, 200];
const lendingId = [3, 4];
const renterAddress = ["0x465DCa9995D6c2a81A9Be80fBCeD5a770dEE3daE", "0x465DCa9995D6c2a81A9Be80fBCeD5a770dEE3daE"];
const amountToPay = [
  parseFixed("1", RESOLVERS[RenftContracts.WHOOPI_FUJI][PaymentToken.USDC]).toString(),
  parseFixed("1", RESOLVERS[RenftContracts.WHOOPI_FUJI][PaymentToken.USDC]).toString()
];

txn = await whoopi.pay(
  castleCrushNftAddress,
  tokenId,
  lendingId,
  renterAddress,
  amountToPay,
  // { gasLimit: 1000000 }
);
receipt = await txn.wait();

To learn about how to query for the on-chain data in a decentralized way, you can continue reading here.

Last updated