Validator Staking: A Quick Start

Customer Staking Integration Guide

In this guide, you'll learn how to integrate customer staking into your website to allow your customers to stake their validators on 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 offer non-custodial staking services to their customers. With this system, customers maintain control of their own wallets and funds. This solution is ideal if you're an institution looking to build your own staking offering using our Enterprise Staking product as a foundation.

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.

Post-Pectra Staking Changes

The Pectra upgrade introduced a major change to Ethereum staking: withdrawal credentials now support two formats that define how rewards are handled:

  • 0x01 (Classic Staking) → Rewards are withdrawn to a fixed payout address.
  • 0x02 (Compounding Staking) → Rewards are restaked automatically, increasing the validator's balance.

This provides your customers with more flexibility: either opt for predictable payouts or maximize compounding yield for long-term growth.

0x01 vs 0x02 Staking

TypePrefixBehaviorBenefitsBest For
0x010x01Rewards and principal are sent to a withdrawal address.Liquidity: customers can receive rewards regularly and move them.Customers that need predictable ETH flow, regular access to rewards.
0x020x02Rewards are compounded by being added directly to the validator's balance.Maximized long-term returns through automatic compounding.Long-term holders, customers who don't need regular payouts and want to grow validator balance.
⚖️

Rule of Thumb:

  • Use V1 API if your customers need liquidity and frequent access to staking rewards.
  • Use V2 API if your customers want to maximize yield by compounding rewards into the validator balance.

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.hoodi.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 validators. To do so, they will need to sign a message using their wallet.

Generate Message

To generate a message for your customer to sign, choose between v1 and v2 APIs based on the desired staking type:

V1 API - 0x01 (Classic Staking)

For traditional staking where rewards are withdrawn to a fixed address:

curl -X GET "https://api.hoodi.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"

Explanation of Fields:

  • count: Number of validators (each will be exactly 32 ETH)
  • address: The wallet address from which Ether will be deposited

V2 API - 0x02 (Compounding Staking)

For compounding staking with flexible deposit amounts:

curl -X GET "https://api.hoodi.stake.fish/v1/eth/stake/v2/deposit-challenge?amounts=32&amounts=64&amounts=48&address=DEPOSITOR_WALLET_ADDRESS" \
    -H "Content-Type: application/json" \
    -H "Authorization: YOUR_PUBLIC_API_KEY"

Explanation of Fields:

  • amounts: Deposit amounts in Ether
  • address: The wallet address from which Ether will be deposited
📘

Important

Both APIs require Public API Key.

Important Notes:

  • V1 API: Always creates 0x01 validators with exactly 32 ETH each
  • V2 API: Always creates 0x02 validators with flexible amounts (32 ETH to 2048 ETH)

Example Responses:

V1 API Response

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

V2 API Response

{
  "challenge": "I confirm I would like to deposit [32.0, 64.0, 48.0] ETH for staking 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 (or 1 hour for smart contract wallets).

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 { hoodi } from 'viem/chains'
 
export const walletClient = createWalletClient({
  chain: hoodi,
  transport: custom(window.ethereum!),
})
 
export const [account] = await walletClient.getAddresses()

const signature = await walletClient.signMessage({ 
  account,
  message: "I confirm I would like to deposit [32.0, 64.0, 48.0] ETH for staking 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 validators for staking. Choose between v1 and v2 APIs based on your staking preference:

V1 API - 0x01 (Classic Staking)

For traditional staking with fixed 32 ETH deposits:

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

V2 API - 0x02 (Compounding Staking)

For compounding staking with flexible deposit amounts:

curl -X POST "https://api.hoodi.stake.fish/v1/eth/stake/v2/create-validators" \
    -H "Content-Type: application/json" \
    -H "Authorization: YOUR_PUBLIC_API_KEY" \
    --data '{
        "amounts": ["32", "64", "48"],
        "depositor": "DEPOSITOR_WALLET_ADDRESS",
        "withdrawalCredentials": "DEPOSITOR_WALLET_ADDRESS",
        "message": "I confirm I would like to deposit [32.0, 64.0, 48.0] ETH for staking and this request is valid until 1725621178.",
        "signature": "0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"
    }'
📘

Important

Both APIs require Public API Key.

Key Differences:

  • V1 API: Always creates 0x01 validators with exactly 32 ETH each
  • V2 API: Always creates 0x02 validators with flexible amounts (32 ETH to 2048 ETH)

V1 API Fields:

  • count: Number of validators to create (each will be exactly 32 ETH)
  • depositor: The address from which Ether will be deposited
  • withdrawalCredentials: The withdrawal address (will be formatted as 0x01)
  • message: The signed message from the previous step
  • signature: The wallet signature

V2 API Fields:

  • amounts: Array of deposit amounts in gwei (flexible amounts from 32 ETH to 2048 ETH)
  • depositor: The address from which Ether will be deposited
  • withdrawalCredentials: The withdrawal address (will be formatted as 0x02)
  • message: The signed message from the previous step
  • signature: The wallet signature

Example Responses:

V1 API Response (0x01 - Classic Staking)

{
  "pubkeys": "0xb078b58f3801a6afe94ba4233dd96a6ad61f56c4c3e8be69c4a89820efe14db47dc53ea84ae8751e2493a9efdfcd7d28b22447d996887afec5631410abd417de030e659c6d6c33d99af5db930f50a05f7f983bbb52778dae1244484469b8d675",
  "withdrawalCredentials": "0x0100000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee650100000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee650100000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee65",
  "signatures": "0x89a39b1e8c9488ba1f65af7f0f6fc50641bb2b50e7cb7133729fbc1ff1fa1c25c169a35a685c5ce9d53db42807979ef61689e860fa3001ff9d475c7171fa19fd96717ad3089f3006139076282d87fff1703db57b56934a055207fe8e9978a12a",
  "depositDataRoots": [
    "0xc01bd131d4307686b280a1d4631550c1d81d09d386582980ac29a657ade2521e",
    "0xd12ce242e5418797c391b5d1e2e6c061e82d0ae3974920c0ba40b768bef3632f",
    "0xe23df353f6529808d4a2c6d2f3f7d172f93e1bf4085a31d1cb51c879cff4743g"
  ]
}

V2 API Response (0x02 - Compounding Staking)

{
  "pubkeys": "0xc075d448c3c96cd9c7297b2e6a3f4d59b5c2a9e8f1e6d8c4b9a7f5e3d1c9b7ab22447d996887afec5631410abd417de030e659c6d6c33d99af5db930f50a05f7f983bbb52778dae1244484469b8d675a123e456f789a012b345c678d901e234f",
  "withdrawalCredentials": "0x0200000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee650200000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee650200000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee65",
  "signatures": "0x95c2a9e8f1e6d8c4b9a7f5e3d1c9b7a3f4d59b5c2a9e8f1e6d8c4b9a7f5e3d1c9b7ac8f6e4d2b0a8f6e4d2b0a8f6e4d2b0a8f6e4d2b0a8f6e4d2b0a8f6e4d2b0567d890e123f456a789b012c345d678e901f234a567b890c123d456e789f012",
  "depositDataRoots": [
    "0xa45fe789a012b345c678d901e234f567a890b123c456d789e012f345a678b901",
    "0xb56gf890b123c456d789e012f345a678b901c234d567e890f123a456b789c012",
    "0xc67ha901c234d567e890f123a456b789c012d345e678f901a234b567c890d123"
  ]
}

Note the difference in withdrawal credentials:

  • V1 API: All withdrawal credentials start with 0x01 (classic staking)
  • V2 API: All withdrawal credentials start with 0x02 (compounding staking)

Deposit Validator

Once the validators are prepared and you've generated the necessary deposit details, the final step is to deposit your validators on Ethereum. This process involves sending a 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 total value (sum of all Ether amounts being staked).

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 { hoodi } from 'viem/chains'
 
export const walletClient = createWalletClient({
  chain: hoodi,
  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 endpoint
const depositPayload = {
  pubkeys: '0xc075d448c3c96cd9c7297b2e6a3f4d59b5c2a9e8f1e6d8c4b9a7f5e3d1c9b7ab22447d996887afec5631410abd417de030e659c6d6c33d99af5db930f50a05f7f983bbb52778dae1244484469b8d675a123e456f789a012b345c678d901e234f',
  withdrawalCredentials: '0x0200000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee650200000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee650200000000000000000000001ec7fa23a8468f1f3a135bfefe6d678e1657ee65',
  signatures: '0x95c2a9e8f1e6d8c4b9a7f5e3d1c9b7a3f4d59b5c2a9e8f1e6d8c4b9a7f5e3d1c9b7ac8f6e4d2b0a8f6e4d2b0a8f6e4d2b0a8f6e4d2b0a8f6e4d2b0a8f6e4d2b0567d890e123f456a789b012c345d678e901f234a567b890c123d456e789f012',
  depositDataRoots: [
    '0xa45fe789a012b345c678d901e234f567a890b123c456d789e012f345a678b901',
    '0xb56gf890b123c456d789e012f345a678b901c234d567e890f123a456b789c012',
    '0xc67ha901c234d567e890f123a456b789c012d345e678f901a234b567c890d123'
  ]
}

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

// Calculate total value: 32 + 64 + 48 = 144 ETH (for V2 example)
// For V1: 32 + 32 + 32 = 96 ETH (3 validators × 32 ETH each)
const totalValue = 32n + 64n + 48n // ETH amounts for V2 example
const valueInWei = totalValue * (10n ** 18n)

const txHash = await walletClient.sendTransaction({
  data: calldata,
  to: BATCH_DEPOSIT_ADDRESS,
  value: valueInWei,
  type: 'eip1559',
})

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

Important Notes:

  • 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.
  • The value field must be the total sum of all deposit amounts in wei.
  • V1 example: 3 validators × 32 ETH = 96 ETH total
  • V2 example: 32 ETH + 64 ETH + 48 ETH = 144 ETH total

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 Hoodi Etherscan or your preferred block explorer.

Validator Activation: Once the transaction is confirmed, your validators are 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 validators will be queued for activation, which can take anywhere from a few minutes to several days, depending on the current validator queue on Ethereum.

Different Staking Behaviors:

  • V1 validators (0x01): Send rewards and principal to the withdrawal address when exited
  • V2 validators (0x02): Compound rewards automatically, growing their balances over time

✅ Your customers have successfully staked their Ethereum validators using stakefish API. Choose V1 for traditional staking with predictable payouts, or V2 for compounding staking with flexible amounts.