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.
Begin by adding the required libraries to your project. Execute the following commands in your terminal:
Using npm:
npminstallwagmiviem
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.constabi= [ { type:'function', name:'deployStaking', inputs: [{ name:'_user', type:'address', internalType:'address' }], outputs: [{ name:'', type:'address', internalType:'address' }], stateMutability:'nonpayable', },] asconst// The address of the staking pool factory contract on sepolia.// For a production environment use the mainnet address: 0x5d09145E1E798c7a885e49a6FC4f0542ce231A47constcontractAddress='0x2dddee42069b66a290c2979d62eb498692492ed9'exportdefaultfunctionDeployStakingComponent() {const [targetAddress,setTargetAddress] =useState('')const { writeContract } =useWriteContract()consthandleSubmit= (e:React.FormEvent) => {e.preventDefault() // Prevent the form from submitting in the traditional waywriteContract({ abi: abi, address: contractAddress, functionName:'deployStaking', args: [targetAddress asAddress], }) }// Render a simple form with an input for the target address and a submit buttonreturn (<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.
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 viemwriteContract 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 functionconstabi= [ { type:'function', name:'addRewards', inputs: [ { name:'_donor', type:'address', internalType:'address' }, { name:'_amount', type:'uint256', internalType:'uint256' }, ], outputs: [], stateMutability:'nonpayable', },] asconstexportdefaultfunctionAddRewardsComponent() {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 addressconstaccount=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 addresssetDonorAddress(account.address) } }, [account])consthandleSubmit= (e:React.FormEvent) => {e.preventDefault()writeContract({ abi: abi, address: poolAddress asAddress, functionName:'addRewards', args: [donorAddress asAddress,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 testnetconstregistryAddress='0x18764BEA22e63e7F58D3cF454D94e279bA0f3F3C'asAddress// The null address is used to represent $ETH in a rewardSwap transactionconstnullAddress='0x0000000000000000000000000000000000000000'asAddressexportdefaultfunctionRewardSwapComponent() {const [userAddress,setUserAddress] =useState<Address|''>('')const [rareAmountIn,setRareAmountIn] =useState('')const { writeContract,error } =useWriteContract()// Hook to fetch the reward accumulator address for the userconst { 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 asAddress], query: { enabled:!!userAddress }, })// Hook to fetch the estimated swap priceconst { 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.