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

SDK does heavy lifting and does all these steps in a single function. This function is deposit function. This tutorial shows both the ways of staking with the SDK. The later section shows the inner workings of the deposit function.

Here's how you can use the deposit function:

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 "mainnet" 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 = "DoNotCopyMe@123";
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;

const tx = await sdk.deposit(
keystore,
PASSWORD,
depositData,
PRIV_KEY,
user=null
);

console.log("tx: ", tx);
};

main();

The deposit function can be used in multiple ways:

  • Via a dapp. While using the dapp, user can provide sign the transaction without providing the private key. The dapp when connected to a wallet would redirect user to a wallet pop up where they can sign the transaction
  • Via a representative. A user can assign another user to be representative and perform tasks on their behalf. For example, Alice has 32 ETH but isn't very familiar with the staking process. Whereas Bob has multiple node running services and can quickly spin up an Ethereum node. So, Alice can make Bob her representative while keeping the 32 ETH still with herself. Bob generates validator credentials and calls the deposit function. For this, Bob will have to provide an optional parameter i.e. user (Alice's Ethereum execution layer address will be the user parameter). If all parameters are correct, deposit function will return Bob with an encoded data. Alice can easily sign and send a transaction with this encoded data to deposit her 32 ETH. There are many more ways to carry out this transaction. Alice and Bob can create an escrow which holds 32 ETH or maybe Alice trusts Bob with her 32 ETH and can directly make the deposit.

Following section goes through the internals of the deposit function and how someone can stake a validator without calling the SDK's deposit function.

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 "mainnet" 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 = "DoNotCopyMe@123";

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 wait function.

await tx.wait();

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. By signing the payload, the user confirms that they are the owner or the representative of the BLS credentials. If the user is indeed the rightful owner/representative of the credentials, the function returns a payload.

// 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. BLS Authentication is necessary as it validates that the keystore and the deposit data are correct and can be used to spin up a validator. This is a pre-deposit step which returns an authentication package required to stake the 32 ETH for a validator.

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

Please be very cautious with this step as it requires ETH. Make sure you are connected to the correct network and you really want to stake a vlaidator. Once done, the 32 ETH are sent to Ethereum Foundation's Deposit Contract depending on the network you are on (goerli or mainnet). 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 main = async () => {

// Get provider and signer instance
// The SDK supports both "goerli" and "mainnet" 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 = "DoNotCopyMe@123";
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
);

await tx.wait();

// 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();