Integrating Rare Staking Pools

Deployment of staking pools

This guide provides examples on how to programmatically deploy a staking pool as well as other common protocol interactions. It is designed for developers who wish to integrate Rare Protocol's staking functionality into their applications. The guide includes detailed code examples with the use of wagmi and viem for ethereum interactions.

Prerequisites

Before proceeding, ensure your development environment is set up with the necessary libraries:

  • wagmi: A collection of React Hooks simplifying Ethereum blockchain interactions.

  • viem: A utility library for Ethereum address and transaction management.

  • Test $RARE tokens: Contracts are deployed on the sepolia testnet, which can make the development process significantly easier and affordable.

Deployed Contracts

STAKING FACTORY:

Mainnet: 0x5d09145E1E798c7a885e49a6FC4f0542ce231A47

Sepolia: 0x2dddee42069b66a290c2979d62eb498692492ed9

STAKING REGISTRY

Mainnet:0x0c891cBA9A617e6B06c9B6FBBD340d61e4Dd313b

Sepolia: 0x18764BEA22e63e7F58D3cF454D94e279bA0f3F3C

Install Dependencies

Begin by adding the required libraries to your project. Execute the following commands in your terminal:

Using npm:

npm install wagmi viem

Staking Pool Deployment

This example demonstrates how to create a simple user interface for deploying a staking pool with a single button click.

import { useState } from 'react'
import { Address } from 'viem'
import { useWriteContract } from 'wagmi'

// The abi for the single necessary function to deploy a staking pool
// Importing the entire contract's abi will work as well.
// The useWriteContract hook will only use the abi for the function you specify in the writeContract call.
const abi = [
  {
    type: 'function',
    name: 'deployStaking',
    inputs: [{ name: '_user', type: 'address', internalType: 'address' }],
    outputs: [{ name: '', type: 'address', internalType: 'address' }],
    stateMutability: 'nonpayable',
  },
] as const

// The address of the staking pool factory contract on sepolia.
// For a production environment use the mainnet address: 0x5d09145E1E798c7a885e49a6FC4f0542ce231A47
const contractAddress = '0x2dddee42069b66a290c2979d62eb498692492ed9'

export default function DeployStakingComponent() {
  const [targetAddress, setTargetAddress] = useState('')
  const { writeContract } = useWriteContract()

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault() // Prevent the form from submitting in the traditional way
    writeContract({
      abi: abi,
      address: contractAddress,
      functionName: 'deployStaking',
      args: [targetAddress as Address],
    })
  }

  // Render a simple form with an input for the target address and a submit button
  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="targetAddress">Target Address:</label>
      <input
        id="targetAddress"
        type="text"
        value={targetAddress}
        onChange={(e) => setTargetAddress(e.target.value)}
        placeholder="Enter target address"
        required
      />
      <button type="submit">Deploy Staking Pool</button>
    </form>
  )
}

Staking

A staking component might look very similar. The user inputs the pool address and the amount they wish to stake. The component then uses the useWriteContract hook to call the stake function on the pool contract.

import { useState } from 'react'
import { Address, parseEther } from 'viem'
import { useWriteContract } from 'wagmi'

const abi = [
  {
    type: 'function',
    name: 'stake',
    inputs: [{ name: '_amount', type: 'uint120', internalType: 'uint120' }],
    outputs: [],
    stateMutability: 'nonpayable',
  },
] as const

export default function Stake() {
  const [poolAddress, setPoolAddress] = useState('')
  const [amountToStake, setAmountToStake] = useState('')
  const { writeContract } = useWriteContract()

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    writeContract({
      abi: abi,
      address: poolAddress as Address,
      functionName: 'stake',
      args: [parseEther(amountToStake)],
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="poolAddress">Pool Address:</label>
      <input
        id="poolAddress"
        type="text"
        value={poolAddress}
        onChange={(e) => setPoolAddress(e.target.value)}
        placeholder="Enter pool address"
        required
      />
      <label htmlFor="amountToStake">Amount to Stake:</label>
      <input
        id="amountToStake"
        type="text"
        value={amountToStake}
        onChange={(e) => setAmountToStake(e.target.value)}
        placeholder="Amount"
        required
      />
      <button type="submit">Deploy Staking Pool</button>
    </form>
  )
}

Add Rewards

This example demonstrates how to create a simple interface allowing users to add rewards to a staking pool with a single button click.

This might also be a backend process if you're a platform owner, who wants to reward pools for activity on your platform. In that case, you would use a viem writeContract backend process, rather than a React component.

Note tht AddRewards can only be called with the _amount denoted in $RARE tokens.

import { useEffect, useState } from 'react'
import { Address, parseEther } from 'viem'
import { useAccount, useWriteContract } from 'wagmi'

// Define the ABI to include the addRewards function
const abi = [
  {
    type: 'function',
    name: 'addRewards',
    inputs: [
      { name: '_donor', type: 'address', internalType: 'address' },
      { name: '_amount', type: 'uint256', internalType: 'uint256' },
    ],
    outputs: [],
    stateMutability: 'nonpayable',
  },
] as const

export default function AddRewardsComponent() {
  const [poolAddress, setPoolAddress] = useState('')
  const [donorAddress, setDonorAddress] = useState('')
  const [rewardAmount, setRewardAmount] = useState('')
  const { writeContract } = useWriteContract()
  // Wagmi Hook to use the connected user's wallet address as the donor address
  const account = useAccount()

  useEffect(() => {
    if (account.status === 'connected') {
      // The donor address is the address that should be credited with the reward
      // In this we will set the donor address to the connected user's wallet address
      setDonorAddress(account.address)
    }
  }, [account])

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    writeContract({
      abi: abi,
      address: poolAddress as Address,
      functionName: 'addRewards',
      args: [donorAddress as Address, parseEther(rewardAmount)],
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="poolAddress">Pool Address:</label>
      <input
        id="poolAddress"
        type="text"
        value={poolAddress}
        onChange={(e) => setPoolAddress(e.target.value)}
        placeholder="Enter pool address"
        required
      />
      <label htmlFor="rewardAmount">Reward Amount:</label>
      <input
        id="rewardAmount"
        type="number"
        value={rewardAmount}
        onChange={(e) => setRewardAmount(e.target.value)}
        placeholder="Enter reward amount"
        required
      />
      <button type="submit">Add Rewards</button>
    </form>
  )
}

Reward Swaps

This example demonstrates a React component that makes a more complex staking interaction with multiple contracts in order to perform a reward swap operation. The component allows a user to enter their ethereum address and the amount of $RARE tokens they wish to swap.

  • Registry contract: This contract provides the address of the reward accumulator contract for a given user address.

  • Accumulator contract: This contract contains functions to estimate the swap price for $RARE tokens and to perform the swap operation.

The user inputs their target user's ethereum address and the amount of $RARE tokens they want to swap.

The component uses useReadContract to fetch the address of that user's pool's reward accumulator contract from the staking registry.

The component fetches the estimated swap price for the input $RARE tokens from the reward accumulator, showing the user the estimated swap price.

The user can then execute the swap by clicking the Perform Reward Swap button, which calls the rewardSwap function in the Accumulator contract through useWriteContract, and will receive the computed $ETH in return.

import { useState } from 'react'
import { Address, parseEther } from 'viem'
import { useReadContract, useWriteContract } from 'wagmi'

// The staking registry on Sepolia testnet
const registryAddress = '0x18764BEA22e63e7F58D3cF454D94e279bA0f3F3C' as Address
// The null address is used to represent $ETH in a rewardSwap transaction
const nullAddress = '0x0000000000000000000000000000000000000000' as Address

export default function RewardSwapComponent() {
  const [userAddress, setUserAddress] = useState<Address | ''>('')
  const [rareAmountIn, setRareAmountIn] = useState('')
  const { writeContract, error } = useWriteContract()

  // Hook to fetch the reward accumulator address for the user
  const { data: accumulatorAddress } = useReadContract({
    abi: [
      {
        type: 'function',
        name: 'getRewardAccumulatorAddressForUser',
        inputs: [{ name: '_user', type: 'address', internalType: 'address' }],
        outputs: [{ name: '', type: 'address', internalType: 'address' }],
        stateMutability: 'view',
      },
    ],
    address: registryAddress,
    functionName: 'getRewardAccumulatorAddressForUser',
    args: [userAddress as Address],
    query: { enabled: !!userAddress },
  })

  // Hook to fetch the estimated swap price
  const { data: swapPrice } = useReadContract({
    abi: [
      {
        type: 'function',
        name: 'estimateRarePrice',
        inputs: [
          { name: '_tokenOut', type: 'address', internalType: 'address' },
          {
            name: '_rareAmountIn',
            type: 'uint128',
            internalType: 'uint128',
          },
        ],
        outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }],
        stateMutability: 'view',
      },
    ],
    address: accumulatorAddress,
    functionName: 'estimateRarePrice',
    args: [nullAddress, parseEther(rareAmountIn.toString())],
    query: { enabled: !!rareAmountIn },
  })

  // The transaction will be simulated and any errors thrown before asking for confirmation.
  if (error) return <div>Error: {error.message}</div>

  return (
    <div>
      <input
        type="text"
        value={userAddress}
        onChange={(e) => setUserAddress(e.target.value as Address)}
        placeholder="Enter User Address"
      />
      <input
        type="text"
        value={rareAmountIn}
        onChange={(e) => setRareAmountIn(e.target.value)}
        placeholder="Enter RARE Amount In"
      />
      <button
        onClick={() => {
          writeContract({
            abi: [
              {
                type: 'function',
                name: 'rewardSwap',
                inputs: [
                  {
                    internalType: 'address',
                    name: '_tokenOut',
                    type: 'address',
                  },
                  {
                    internalType: 'uint256',
                    name: '_minAmountOut',
                    type: 'uint256',
                  },
                  {
                    internalType: 'uint128',
                    name: '_rareIn',
                    type: 'uint128',
                  },
                ],
                outputs: [],
                stateMutability: 'nonpayable',
              },
            ],
            address: accumulatorAddress!,
            functionName: 'rewardSwap',
            args: [nullAddress, swapPrice, parseEther(rareAmountIn)],
          })
        }}
      >
        Perform Reward Swap
      </button>
    </div>
  )
}

Tip: use the api.

Necessary values, like pool or other contract addresses, can also be retrieved via the subgraph or api. This can be useful for a more dynamic application. And it is cheaper than making contract calls to the blockchain to look these up.

Automatically Rewarding Pools on Chain

The Bazaar is a contract that facilitates the listing, escrow, and settlement of NFT sales. It also handles fees to the protocol, and rewards to staking pools. 3% of all sales, and an additional 15% of primary sales of NFTs are captured by the network. From which 1% of the sale price is rewarded to the staking pool of the seller.

The Bazaar contract is not the only contract that can reward staking pools. Any smart contract can call the AddRewards function with $RARE to reward a pool. This can be useful for rewarding pools for activity on your platform, or for rewarding pools for other reasons.

function addRewards(address _donor, uint256 _amount) external;

Note the AddRewards function can only be called with the _amount denoted in $RARE tokens. Since reward eligible transactions might not natively include the $RARE token, reward accumulator contracts are paired with each staking pool, to act as a bridge. The reward accumulator contract is responsible for supporting the conversion other currencies and distributing the rewards to the stakers. A normal transfer of $ETH to the reward accumulator is all that is needed to reward a pool in $ETH.

Last updated