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:
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.
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 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.