Validator Staking: A Quick Start

In this guide, you'll learn how to integrate customer staking into your website to allow your customers to stake their validators on the Ethereum. It covers all the essentials to get you up to speed in just a few minutes.

🚧

Important

This staking process is designed for institutions that want to enable non-custodial staking for their customers. Customers will retain control of their wallets and funds. If you're looking to build your own staking solution on top of our Enterprise Staking product.

What’s a Validator?

Validators are the heart of the Ethereum network. Before you proceed with staking, it’s essential to understand their role.

A validator is an entity that participates in the consensus layer by proposing and validating new blocks on the blockchain. Each validator must stake a minimum of 32 Ether (up to a maximum of 2048 Ether) as collateral to ensure honest behavior. Validators play a crucial role in maintaining the network's security and integrity. In return, they earn rewards in the form of Ether.

However, validators that misbehave can face penalties, including slashing, which can result in a significant loss of staked Ether.

Check Remaining Validator Public Keys

While the Stakefish API simplifies the staking process to a single API call followed by a blockchain transaction, it's essential to verify that your organization has enough allocated public keys. By performing this check, you ensure that your staking operations are carried out efficiently and within the limits of your allocated resources.

Use the following API to check how many public keys are currently available for your organization:

curl -X GET "https://api.testnet.stake.fish/v1/eth/stake/v1/remaining-keys" \
     -H "Authorization: YOUR_PUBLIC_API_KEY"

📘

Important

This API requires Public API Key.

This endpoint returns the number of remaining public keys as text/plain regardless of your requested response type. For example, a response of 716 indicates that 716 public keys are available for staking.

Verify Wallet Ownership

Your customer must confirm they have control over their wallet and enough Ether balance to stake a validator. To do so, they will need to sign a message using their wallet.

Generate Message

To generate message for your customer to sign, send the following GET request to the Stakefish API:

curl -X GET "https://api.testnet.stake.fish/v1/eth/stake/v1/deposit-challenge?count=VALIDATOR_COUNT&address=DEPOSITOR_WALLET_ADDRESS" \
    -H "Content-Type: application/json" \
    -H "Authorization: YOUR_PUBLIC_API_KEY"

📘

Important

This API requires Public API Key.

Explanation of Fields:

  • VALIDATOR_COUNT: Specifies the number of validators you want to create. In this example, count is set to 1, indicating that our customer will be creating a single validator. You can create up to 100 validators in one request.
  • DEPOSITOR_WALLET_ADDRESS: This is the address from which Ether will be deposited.

Example Response:

After sending the request, you’ll receive a response similar to the following:

{
  "challenge": "I confirm I would like to stake 1 validator(s) and this request is valid until 1725621178."
}

This is the message your customers will need to sign before staking their validators. Note that the message has an expiration time of 5 minutes.

Customer Signs Message

Once you generated the message for your customer, they need to sign it with their wallet.

import { createWalletClient, custom } from 'viem'
import { holesky } from 'viem/chains'
 
export const walletClient = createWalletClient({
  chain: holesky,
  transport: custom(window.ethereum!),
})
 
export const [account] = await walletClient.getAddresses()

const signature = await walletClient.signMessage({ 
  account,
  message: "I confirm I would like to stake 1 validator(s) and this request is valid until 1725621178.",
})
// Example output: "0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"

In this example, we used library called viem.

This will return a signature that will be used in the staking request.

Customer’s Browser

If your customer uses the MetaMask browser extension, they will see the following popup. They need to click the "Sign" button.

Create Validator

After obtaining the wallet signature and confirming that you have enough allocated public keys, the next step is to prepare your validator for staking. This involves creating a validator with the necessary deposit details using Stakefish API.

To create a validator, send the following POST request to our API:

curl -X POST "https://api.testnet.stake.fish/v1/eth/stake/v1/create-validators" \
    -H "Content-Type: application/json" \
    -H "Authorization: YOUR_PUBLIC_API_KEY" \
    --data '{
        "count": "1",
        "depositor": "DEPOSITOR_WALLET_ADDRESS",
        "withdrawalCredentials": "DEPOSITOR_WALLET_ADDRESS",
        "message": "I confirm I would like to stake 1 validator(s) and this request is valid until 1725621178.",
        "signature": "0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"
    }'

📘

Important

This API requires Public API Key.

Explanation of Fields:

  • count: Specifies the number of validators you want to create. In this example, count is set to 1, indicating that we're creating a single validator. You can create up to 100 validators in one request.
  • depositor: This is the address from which Ether will be deposited.
  • withdrawalCredentials: The address where rewards and staked Ether will be withdrawn. Ensure this is your customer's wallet address.
  • message: The message signed by your customer (from the previous step).
  • signature: The message signature returned by the signing call (from the previous step).

Example Response:

After sending the POST request, you’ll receive a response similar to the following:

{
  "pubkeys": "0xb078b58f3801a6afe94ba4233dd96a6ad61f56c4c3e8be69c4a89820efe14db47dc53ea84ae8751e2493a9efdfcd7d28",
  "withdrawalCredentials": "0x0100000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee65",
  "signatures": "0x89a39b1e8c9488ba1f65af7f0f6fc50641bb2b50e7cb7133729fbc1ff1fa1c25c169a35a685c5ce9d53db42807979ef61689e860fa3001ff9d475c7171fa19fd96717ad3089f3006139076282d87fff1703db57b56934a055207fe8e9978a12a",
  "depositDataRoots": [
    "0xc01bd131d4307686b280a1d4631550c1d81d09d386582980ac29a657ade2521e"
  ]
}

Deposit Validator

Once the validator is prepared and you've generated the necessary deposit details, the final step is to deposit your validator on Ethereum. This process involves sending deposit transaction on Ethereum.

Encode the Batch Deposit Calldata:

Use the encodeBatchDepositCalldata function to encode the public keys, withdrawal credentials, signatures, and deposit data roots into a single calldata string. This calldata will be sent to the batch deposit contract.

Send the Transaction

Using the walletClient, send a transaction to the batch deposit contract address. The transaction should include the encoded calldata, the recipient address (batch deposit contract), and the value (amount of Ether to be staked, e.g., 32 ETH per validator).

Check the Transaction Status

After sending the transaction, you'll receive a transaction hash. Use this hash to monitor the transaction status on Etherscan or directly via viem library.

Here’s the completed code snippet:

import { createWalletClient, custom } from 'viem'
import { holesky } from 'viem/chains'
 
export const walletClient = createWalletClient({
  chain: holesky,
  transport: custom(window.ethereum!),
})
 
export const [account] = await walletClient.getAddresses()

function encodeBatchDepositCalldata(pubkeys, withdrawalCredentials, signatures, depositDataRoots) {
  return viem.encodeFunctionData({
    abi: [viem.parseAbiItem('function batchDeposit(bytes,bytes,bytes,bytes32[])')],
    functionName: 'batchDeposit',
    args: [pubkeys, withdrawalCredentials, signatures, depositDataRoots],
  })
}

// This is obtained from the create-validators OR create-validators-admin endpoint
const depositPayload = {
  pubkeys: '0xb22447d996887afec5631410abd417de030e659c6d6c33d99af5db930f50a05f7f983bbb52778dae1244484469b8d675',
  withdrawalCredentials: '0x0100000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee65',
  signatures: '0x93c1c8305c66a3f9016e0dbd616038170e15a7984bbae7489f2ff476d8e4d910c1b1dcf4d30c547f2c415b32b16f36f3033d92751f32e922999352cb0f7b6a97574612e8c9cb8cdd983e49d2c59022f2fbadd42c3b4bcf098113b3a48e65b3f9',
  depositDataRoots: [
    '0x8ec08305d04cc0738a5bbc571494b06ea7f7c67be00723664da8d6d098e5e053'
  ]
}

const calldata = encodeBatchDepositCalldata(
  depositPayload.pubkeys,
  depositPayload.withdrawalCredentials,
  depositPayload.signatures,
  depositPayload.depositDataRoots,
)

const txHash = await walletClient.sendTransaction({
  data: calldata,
  to: BATCH_DEPOSIT_ADDRESS,
  value: 32n * (10n **18n),
  type: 'eip1559',
})

console.log("Transaction Hash: ", txHash);

Explanation of Constants:

  • BATCH_DEPOSIT_ADDRESS: This is the contract address used to perform batch deposits of validators. You'll receive this address from Stakefish once your account is set up.

If your customer uses MetaMask browser extension they will see the following popup. They will need to confirm the transaction:

Final Steps

After sending the transaction, your Ethereum client will return a transaction hash. You can use this hash to monitor the transaction's progress on Holesky Etherscan or your preferred block explorer.

Deposit transaction on Etherscan

Validator Activation: Once the transaction is confirmed, your validator is in the process of being activated. The deposit typically takes between 16 to 24 hours to be processed by the consensus layer. After processing, your validator will be queued for activation, which can take anywhere from a few minutes to several days, depending on the current validator queue on Ethereum.