Skip to main content

Balance Reporting using the Stakehouse SDK

The ETH balance of a validator can fluctuate over time. The balance increases if the validator successfully attests or proposes a block whereas the balance decreases due to slashing or being offline.
The increase in balance needs to be reflected in the dETH balance of the user’s wallet. This is achieved with the help of the Deposit Router and Balance Reporter using the SDK. The Deposit Router takes care of all the requests generated to update the balance, verify the reports and generate a valid signature as a result.

Stakehouse's SDK allows validators to easily interact with the Beacon Chain and Deposit Router. The tasks a validator can perform include:

  • Fetching a report from the Beacon Chain.
  • Authenticating the finalized Beacon Chain report and generating a signature for the report.
  • Submitting the report (along with the signature) to the Stakehouse protocol smart contracts in order to get dETH associated with the inflation rewards.

Example Workflow

Alice staked a 32 ETH validator via the Stakehouse Protocol. Over time, she notices that her ETH balance on the Beacon Chain has increased and she would like to get dETH associated with the inflation rewards. The first step is writing a Nodejs script.

[ Step 1 ] Create a Project

The example project is called StakehouseScript

mkdir StakehouseScript && cd StakehouseScript

[ Step 2 ] Adding Files to the Project

touch .env && touch package.json && touch index.js

The contents of package.json will not be discussed, although the dependencies are discussed in the later section.

[ Step 3 ] Adding Parameters to the .env File

The project uses Infura. With some changes to the code, this can be used with other APIs.

INFURA_PROJECT_ID=
INFURA_PROJECT_SECRET=
PRIV_KEY=
BEACON_NODE=

The Private Key being used here is the key of the ETH account paying for the GAS to do the balance report.

[ STEP 4 ] Installing the Dependencies

For this project use dotenv, yargs, ethers, @blockswaplab/stakehouse-protocol-abis and @blockswaplab/stakehouse-sdk

npm i --save dotenv ethers yargs @blockswaplab/stakehouse-protocol-abis @blockswaplab/stakehouse-sdk

The @blockswaplab/stakehouse-protocol-abis package is necessary as the SDK needs it.
Now, all the prerequisites for the script are ready. Next is writing the script in index.js file:

[ STEP 5 ] Creating a Base for the Script

Below are all the packages necessary for the script. This is what the index.js file will look like.

require('dotenv').config();
const { ethers } = require("ethers");
const { getFinalisedEpochReport, reportBalanceIncrease, authenticateReport } = require("@blockswaplab/stakehouse-sdk");
const yargs = require('yargs/yargs');

The next part after including the above set of code, is to access the parameters from the .env file as shown below:

const INFURA_PROJECT_ID = process.env.INFURA_PROJECT_ID;
const INFURA_PROJECT_SECRET = process.env.INFURA_PROJECT_SECRET;
const PRIV_KEY = process.env.PRIV_KEY;
const BEACON_NODE = process.env.BEACON_NODE;

With all the packages and parameters accessible, next write the main function and define the signer and provider to interact with the Infura API:

async function main() {

const provider = new ethers.providers.InfuraProvider("goerli", {
projectId: INFURA_PROJECT_ID,
projectSecret: INFURA_PROJECT_SECRET
});

const signer = new ethers.Wallet(PRIV_KEY, provider);

}

main();

Before using the Stakehouse SDK, automate the script to avoid needing user input. This is done by utilizing the installed yargs package. The SDK requires 2 inputs from the user; These are the BLS Public Key and the Stakehouse Address. Write the function getCommandLineArgs to send the BLS Public Key and Stakehouse Address as a command when running the index.js script.

const getCommandLineArgs = () =>
yargs(process.argv.slice(2))
.option(
'blsPublicKey', {
alias: 'pk',
describe: 'BLS public key of an ETH2 validator',
string: true,
demandOption: true
}
)
.option(
'stakehouseAddr', {
alias: 'addr',
describe: 'Address of the Stakehouse',
string: true,
demandOption: true
}
).argv

Add the following code in the main function:

let { blsPublicKey, stakehouseAddr } = getCommandLineArgs();

const STAKEHOUSE_ADDRESS = stakehouseAddr;
const BLS_PUBLIC_KEY = blsPublicKey;

The base script is now finished. Below is the index.js file.:

require('dotenv').config();
const { ethers } = require("ethers");
const { getFinalisedEpochReport, reportBalanceIncrease, authenticateReport } = require("@blockswaplab/stakehouse-sdk");
const yargs = require('yargs/yargs');

const INFURA_PROJECT_ID = process.env.INFURA_PROJECT_ID;
const INFURA_PROJECT_SECRET = process.env.INFURA_PROJECT_SECRET;
const PRIV_KEY = process.env.PRIV_KEY;
const BEACON_NODE = process.env.BEACON_NODE;

const getCommandLineArgs = () =>
yargs(process.argv.slice(2))
.option(
'blsPublicKey', {
alias: 'pk',
describe: 'BLS public key of an ETH2 validator',
string: true,
demandOption: true
}
)
.option(
'stakehouseAddr', {
alias: 'addr',
describe: 'Address of the Stakehouse',
string: true,
demandOption: true
}
).argv

async function main() {

let { blsPublicKey, stakehouseAddr } = getCommandLineArgs();

const STAKEHOUSE_ADDRESS = stakehouseAddr;
const BLS_PUBLIC_KEY = blsPublicKey;

const provider = new ethers.providers.InfuraProvider("goerli", {
projectId: INFURA_PROJECT_ID,
projectSecret: INFURA_PROJECT_SECRET
});

const signer = new ethers.Wallet(PRIV_KEY, provider);
}

main();

[ STEP 6 ] Using the SDK

The following section of this doc will demonstrate how to use the Stakehouse SDK.

i. Fetching a report from the Beacon Chain

Remember Alice, who became a validator and wanted to update her ETH balance? Well, in order to update the balance with the Stakehouse protocol smart contract, we first need some data fetched from the Beacon Chain. The getFinalisedEpochReport function from the SDK allows users to query the Beacon Chain to report the update. getFinalisedEpochReport needs the BLS Public Key of the validator and the Beacon Node URL. Add the following piece of code in the main function.

const finalReport = await getFinalisedEpochReport(BEACON_NODE, BLS_PUBLIC_KEY);
console.log("getFinalisedEpochReport : ", finalReport);

ii. Authenticating the Beacon Chain Report

Now that the report has been fetched from the Beacon Chain, the report needs to be authenticated. This is done by matching the values of the report with the report from three Beacon nodes. If each value in the report is correct, it will return a report with a valid signature. The signature has a validity period; After some time the signature expires, and a new signature with the report needs to be generated. The authenticateReport function from the SDK does this, and all it needs is the finalReport returned by the getFinalisedEpochReport.

const authReport = await authenticateReport(finalReport);
console.log("authenticateReport : ", authReport);

iii. Reporting the Updated Balance

The report has now been authenticated and finalized with a signature. The last step is to report this to the Balance Reporter adaptor. Along with the signer and the authReport obtained from authenticateReport, use the Stakehouse Address and BLS Public Key. Upon successfully reporting the updated balance, it will return the transaction details. The following example, will simply print the transaction hash which can be verified on the Goerli blockchain explorer (reportBalanceIncrease will return the whole transaction). Here's how it's done.

const tx = await reportBalanceIncrease(signer, BLS_PUBLIC_KEY, STAKEHOUSE_ADDRESS, authReport);
console.log("reportBalanceIncrease (Tx Hash) : ", tx.hash);

Here's the final index.js file:

require('dotenv').config();
const { ethers } = require("ethers");
const { getFinalisedEpochReport, authenticateReport, reportBalanceIncrease } = require("@blockswaplab/stakehouse-sdk");
const yargs = require('yargs/yargs');

const INFURA_PROJECT_ID = process.env.INFURA_PROJECT_ID;
const INFURA_PROJECT_SECRET = process.env.INFURA_PROJECT_SECRET;
const PRIV_KEY = process.env.PRIV_KEY;
const BEACON_NODE = process.env.BEACON_NODE;

const getCommandLineArgs = () =>
yargs(process.argv.slice(2))
.option(
'blsPublicKey', {
alias: 'pk',
describe: 'BLS public key of an ETH2 validator',
string: true,
demandOption: true
}
)
.option(
'stakehouseAddr', {
alias: 'addr',
describe: 'Address of the Stakehouse',
string: true,
demandOption: true
}
).argv

async function main() {

let { blsPublicKey, stakehouseAddr } = getCommandLineArgs();

const STAKEHOUSE_ADDRESS = stakehouseAddr;
const BLS_PUBLIC_KEY = blsPublicKey;

const provider = new ethers.providers.InfuraProvider("goerli", {
projectId: INFURA_PROJECT_ID,
projectSecret: INFURA_PROJECT_SECRET
});

const signer = new ethers.Wallet(PRIV_KEY, provider);

const finalReport = await getFinalisedEpochReport(BEACON_NODE, BLS_PUBLIC_KEY);
console.log("getFinalisedEpochReport : ", finalReport);

const authReport = await authenticateReport(finalReport);
console.log("authenticateReport : ", authReport);

const tx = await reportBalanceIncrease(signer, BLS_PUBLIC_KEY, STAKEHOUSE_ADDRESS, authReport);
console.log("reportBalanceIncrease (Tx Hash) : ", tx.hash);
}

main();

[ STEP 7 ] Running the Script

After saving the file, run the command:

node index.js --pk <INSERT_BLS_PUBLIC_KEY> --addr <INSERT_STAKEHOUSE_ADDRESS>

Here's a sample output after running the script:

getFinalisedEpochReport :  {  
blsPublicKey: 'a97f0308ffffd1d253f35ef4d8309059b7d89fd530a9adce...',
withdrawalCredentials: '010000000000000000000000f847b44695d33e9...',
slashed: false,
activeBalance: '32056048806',
effectiveBalance: '32000000000',
exitEpoch: '18446744073709551615',
activationEpoch: '63601',
withdrawalEpoch: '18446744073709551615',
currentCheckpointEpoch: 67718
}
authenticateReport : {
report: {
blsPublicKey: 'a97f0308ffffd1d253f35ef4d8309059b7d89fd530a9adce...',
withdrawalCredentials: '010000000000000000000000f847b44695d33e9...',
slashed: false,
activeBalance: '32056048806',
effectiveBalance: '32000000000',
exitEpoch: '18446744073709551615',
activationEpoch: '63601',
withdrawalEpoch: '18446744073709551615',
currentCheckpointEpoch: 67718
},
deadline: 6222906,
v: 27,
r: '0a41854440af207c71652fa9e6c5dc5fa20feef3200e5d8860f35a20b04aab2a',
s: '0aee10ceee308af93962254dc56747dcb4063d416b2d96cac4bac850897a8b22'
}
reportBalanceIncrease (Tx Hash) : 0x2f34accbc35dad7fe157598f3dc2b15deb6ff903734af41e1145b1370c91663c