Last active
June 22, 2024 23:08
-
-
Save xrchz/d173403003ed4097b8e1873bd27096f1 to your computer and use it in GitHub Desktop.
Ways of getting Rocket Pool Rewards
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ethers } from 'ethers' | |
// tested using a fork of mainnet (where we can impersonate the signers) | |
// e.g. anvil -f http://localhost:8545@20143000 -p 8549 | |
const provider = new ethers.JsonRpcProvider('http://localhost:8549') | |
const rocketStorage = new ethers.Contract('rocketstorage.eth', | |
[ | |
'function getAddress(bytes32) view returns (address)', | |
'function getNodeWithdrawalAddress(address) view returns (address)' | |
], | |
provider) | |
const getRocketContract = async (name, abi) => | |
new ethers.Contract(await rocketStorage['getAddress(bytes32)'](ethers.id(`contract.address${name}`)), abi, provider) | |
// We will use this node as our test subject | |
const nodeEns = '🎉️👯♀️🥳️.eth' | |
const nodeAddress = await provider.resolveName(nodeEns) | |
await provider.send("anvil_impersonateAccount", [nodeAddress]) | |
const signer = await provider.getSigner(nodeAddress) | |
const withdrawalAddress = await rocketStorage.getNodeWithdrawalAddress(nodeAddress) | |
const RPL = await getRocketContract('rocketTokenRPL', ['function balanceOf(address) view returns (uint256)']) | |
const printBalances = async (msg) => { | |
console.log(msg) | |
console.log(`Withdrawal address ETH: ${ethers.formatEther(await provider.getBalance(withdrawalAddress))}`) | |
console.log(`Withdrawal address RPL: ${ethers.formatEther(await RPL.balanceOf(withdrawalAddress))}`) | |
console.log() | |
} | |
// Claim minipool rewards | |
const rocketMinipoolManager = await getRocketContract('rocketMinipoolManager', ['function getNodeMinipoolAt(address node,uint256 index) view returns (address)']) | |
const minipoolAddress = await rocketMinipoolManager.getNodeMinipoolAt(nodeAddress, 0) | |
const minipool = new ethers.Contract(minipoolAddress, ['function distributeBalance(bool rewardsOnly)'], provider) | |
await printBalances('Before distributeBalance') | |
await minipool.connect(signer).distributeBalance(true).then(t => t.wait()) | |
await printBalances('After distributeBalance') | |
// N.B.: minipools also have refund() and close() methods that might be needed for retrieving funds from them. Since they are not primarily about rewards I haven't looked into how to use them in this file | |
// Claim fee distributor rewards | |
const feeDistributorManager = await getRocketContract('rocketNodeDistributorFactory', ['function getProxyAddress(address node) view returns (address)']) | |
const feeDistributorAddress = await feeDistributorManager.getProxyAddress(nodeAddress) | |
const feeDistributor = new ethers.Contract(feeDistributorAddress, ['function distribute()']) | |
await printBalances('Before distribute') | |
await feeDistributor.connect(signer).distribute().then(t => t.wait()) | |
await printBalances('After distribute') | |
// Claim interval rewards (RPL + smoothing pool) | |
import { existsSync, readFileSync, createWriteStream } from 'node:fs' | |
import { Readable } from 'node:stream' | |
const rocketRewardsPool = await getRocketContract('rocketRewardsPool', [ | |
'function getRewardIndex() view returns (uint256)' | |
]) | |
const merkleDistributor = await getRocketContract('rocketMerkleDistributorMainnet', [ | |
'function isClaimed(uint256 index, address node) view returns (bool)', | |
'function claimAndStake(address node, uint256[] index, uint256[] amountRPL, uint256[] amountETH,' + | |
' bytes32[][] merkleProof, uint256 stakeAmount)' | |
]) | |
const latestIndex = await rocketRewardsPool.getRewardIndex() | |
const unclaimedIntervals = [] | |
for (let i = 0; i < latestIndex; i++) | |
if (!(await merkleDistributor.isClaimed(i, nodeAddress))) | |
unclaimedIntervals.push(i) | |
console.log(`Got unclaimed intervals: ${unclaimedIntervals}`) | |
const rewardFileUrl = 'https://github.com/rocket-pool/rewards-trees/raw/main/mainnet/' | |
const intervalRewards = [] | |
for (const i of unclaimedIntervals) { | |
const filename = `rp-rewards-mainnet-${i}.json` | |
if (!existsSync(filename)) { | |
const response = await fetch(`${rewardFileUrl}${filename}`) | |
const readStream = Readable.fromWeb(response.body) | |
const promise = new Promise(resolve => readStream.on('end', resolve)) | |
readStream.pipe(createWriteStream(filename)) | |
await promise | |
} | |
const {nodeRewards} = JSON.parse(readFileSync(filename)) | |
const {smoothingPoolEth, collateralRpl, merkleProof} = nodeRewards[nodeAddress.toLowerCase()] ?? {} | |
intervalRewards.push({ | |
ETH: BigInt(smoothingPoolEth || 0), | |
RPL: BigInt(collateralRpl || 0), | |
proof: merkleProof | |
}) | |
} | |
let totalRPL = 0n | |
const claimIndices = [] | |
const amountRPL = [] | |
const amountETH = [] | |
const proofs = [] | |
for (const [i, {ETH, RPL, proof}] of intervalRewards.entries()) { | |
if (typeof proof === 'undefined') continue | |
claimIndices.push(unclaimedIntervals[i]) | |
amountRPL.push(RPL) | |
amountETH.push(ETH) | |
proofs.push(proof) | |
totalRPL += RPL | |
console.log(`Available rewards for Interval ${claimIndices.at(-1)}:`) | |
console.log(`ETH: ${ethers.formatEther(ETH)}`) | |
console.log(`RPL: ${ethers.formatEther(RPL)}`) | |
console.log() | |
} | |
// restake 10% of the total RPL, retrieve the rest | |
const restakeAmount = totalRPL / 10n | |
const rocketNodeStaking = await getRocketContract('rocketNodeStaking', | |
['function getNodeRPLStake(address node) view returns (uint256)']) | |
const printStake = async () => { | |
const amount = await rocketNodeStaking.getNodeRPLStake(nodeAddress) | |
console.log(`RPL staked: ${ethers.formatEther(amount)}`) | |
} | |
await printBalances('Before claimAndStake') | |
await printStake() | |
await merkleDistributor.connect(signer).claimAndStake( | |
nodeAddress, | |
claimIndices, | |
amountRPL, | |
amountETH, | |
proofs, | |
restakeAmount | |
) | |
await printBalances('After claimAndStake') | |
await printStake() | |
/* expected output: | |
Before distributeBalance | |
Withdrawal address ETH: 0.0 | |
Withdrawal address RPL: 0.0 | |
After distributeBalance | |
Withdrawal address ETH: 0.04920901867 | |
Withdrawal address RPL: 0.0 | |
Before distribute | |
Withdrawal address ETH: 0.04920901867 | |
Withdrawal address RPL: 0.0 | |
After distribute | |
Withdrawal address ETH: 0.06033272080216608 | |
Withdrawal address RPL: 0.0 | |
Got unclaimed intervals: 0,1,2,3,4,5,23 | |
Available rewards for Interval 23: | |
ETH: 0.0 | |
RPL: 21.106327077063228527 | |
Before claimAndStake | |
Withdrawal address ETH: 0.06033272080216608 | |
Withdrawal address RPL: 0.0 | |
RPL staked: 2569.00000000000001149 | |
After claimAndStake | |
Withdrawal address ETH: 0.06033272080216608 | |
Withdrawal address RPL: 18.995694369356905675 | |
RPL staked: 2571.110632707706334342 | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment