Skip to main content

Staking a Validator using the Stakehouse SDK

This tutorial goes through the step by step process of staking a validator via the Stakehouse Protocol using the stakehouse-sdk.

Please note that the user will still have to run their own validator client after the deposit has been registered.

The various steps required to stake a validator are:

  • Generating BLS credentials
  • Registering Validator's initials
  • Signing the data payload
  • Authenticating the BLS keystore
  • Registering the validator

After creating an empty JS project, follow this tutorial.

Environment Variables

In the .env file, the following variables should be added

INFURA_PROJECT_ID=
INFURA_PROJECT_SECRET=
BEACON_NODE=
PRIV_KEY=
ADDRESS=

Installing Dependencies

This project requires the stakehouse-sdk, ethers and dotenv library.

yarn add dotenv ethers @blockswaplab/stakehouse-sdk

Please note that the stakehouse-sdk is at least version 3.0.0

Using the Stakehouse SDK

The first step is to get the provider and signer instance from the Private Key of the associated Execution Layer Account.

require('dotenv').config();
const { StakehouseSDK } = require("@blockswaplab/stakehouse-sdk");
const fs = require('fs');
const { ethers } = require('ethers');

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

const main = async () => {

// Get provider and signer instance
// The SDK supports both "goerli" and "ropsten" network.
// Feel free to try them both.
const provider = new ethers.providers.InfuraProvider("goerli", {
projectId: INFURA_PROJECT_ID,
projectSecret: INFURA_PROJECT_SECRET
});
const signer = new ethers.Wallet(PRIV_KEY, provider);
};

main();

Now that the base script is ready, use the stakehouse-sdk. It is as easy as instantiating the SDK. This is how the main function should look:

const main = async () => {

// Get provider and signer instance
const provider = new ethers.providers.InfuraProvider("goerli", {
projectId: INFURA_PROJECT_ID,
projectSecret: INFURA_PROJECT_SECRET
});
const signer = new ethers.Wallet(PRIV_KEY, provider);

// Instantiate the SDK
const sdk = new StakehouseSDK(signer);
};

Generating BLS Credentials

It is necessary to have a BLS credential to create a validator, because this is what indentifies the validator on Consensus layer. The BLS keystore must be protected by a password.

Please do not copy the password from this project and try to keep it as random and secure as possible.

const PASSWORD = "[email protected]";

Now, to generate the BLS credentials use the generateCredentials function from the utils class of the SDK.

const credentials = await sdk.utils.generateCredentials(PASSWORD);

Once, the credential is generated, it can be written into a JSON file.

await fs.writeFileSync('./credentials.json', JSON.stringify(credentials));

Register Validator's Initials

Before moving to this step, define values from the credentials.json file which will be used more often.

const BLS_PUBLIC_KEY = credentials.depositObject[0].pubkey;
const BLS_SIGNATURE = credentials.depositObject[0].signature;
const depositData = credentials.depositObject;
const keystore = credentials.keystore;

The Stakehouse Protocol keeps note of all the validators registered through it. For this reason, it needs a validator's Execution Layer Address, BLS Public Key and BLS Signature.

To register a validator with the newly created credentials, call the registerValidatorInitials function of the SDK.

// Register validator's initials
const registeringTheValidatorInitals = await sdk.registerValidatorInitials(
ADDRESS,
BLS_PUBLIC_KEY,
BLS_SIGNATURE
);

Since, the script does everything, it is important to make sure the validator's initials are registered. This transaction might take some time. Hence, it will be good to use a delay function.

const delay = async (n) => {
return new Promise(function(resolve){
setTimeout(resolve,n*1000);
});
};

Now, after the registerValidatorInitials transaction, just delay for 15 seconds.

await delay(15);

Signing data payload

In order to Authenticate the BLS keystore, it is necessary to sign the BLS Public Key and the BLS Signature with the associated Execution layer account using it's private key. This can be easliy achieved by calling the getPersonalSignInitialsByPrivateKey function from the utils class of the SDK.

// Sign the Payload with the Private Key
const depositorSignaturePayload = await sdk.utils.getPersonalSignInitialsByPrivateKey(
BLS_PUBLIC_KEY,
BLS_SIGNATURE,
PRIV_KEY
);

BLS Authentication

Now that the data payload is signed and ready to be authenticated, call the BLSAuthentication function of the SDK

// Authenticate the BLS keystore
const blsAuthentication = await sdk.BLSAuthentication(
PASSWORD,
keystore,
depositData,
depositorSignaturePayload
);

Registering Validator with 32 ETH

This is the crucial step which makes a deposit of 32 ETH to the Ethereum Foundation's Deposit Contract. The registerValidator function requires as much as the Execution Layer Address and the BLS Authentication Report to send the 32 ETH for the validator.

// Register Validator
const registerValidatorTx = await sdk.registerValidator(ADDRESS, blsAuthentication);

The Script

All the above steps have been compiled into the final script as shown below.

require('dotenv').config();
const { StakehouseSDK } = require("@blockswaplab/stakehouse-sdk");
const fs = require('fs');
const { ethers } = require('ethers');

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

const delay = async (n) => {
return new Promise(function(resolve){
setTimeout(resolve,n*1000);
});
};

const main = async () => {

// Get provider and signer instance
// The SDK supports both "goerli" and "ropsten" network.
// Feel free to try them both.
const provider = new ethers.providers.InfuraProvider("goerli", {
projectId: INFURA_PROJECT_ID,
projectSecret: INFURA_PROJECT_SECRET
});
const signer = new ethers.Wallet(PRIV_KEY, provider);

// Instantiate the SDK
const sdk = new StakehouseSDK(signer);

// Generate BLS credentials protected by a password
const PASSWORD = "[email protected]";
const credentials = await sdk.utils.generateCredentials(PASSWORD);
await fs.writeFileSync('./credentials.json', JSON.stringify(credentials));

const BLS_PUBLIC_KEY = credentials.depositObject[0].pubkey;
const BLS_SIGNATURE = credentials.depositObject[0].signature;
const depositData = credentials.depositObject;
const keystore = credentials.keystore;

// Register validator's initials
const registeringTheValidatorInitals = await sdk.registerValidatorInitials(
ADDRESS,
BLS_PUBLIC_KEY,
BLS_SIGNATURE
);

// Wait for 15 seconds
await delay(15);

// Sign the Payload with the Private Key
const depositorSignaturePayload = await sdk.utils.getPersonalSignInitialsByPrivateKey(
BLS_PUBLIC_KEY,
BLS_SIGNATURE,
PRIV_KEY
);

// Authenticate the BLS keystore
const blsAuthentication = await sdk.BLSAuthentication(
PASSWORD,
keystore,
depositData,
depositorSignaturePayload
);

// Register Validator
const registerValidatorTx = await sdk.registerValidator(ADDRESS, blsAuthentication);
};

main();