New Validator Creation

Objective

Enable creation of new validators with flexible sizing — from 32 ETH up to 1920 ETH — using 0x01 withdrawal credentials via the standard flow, or 0x02 credentials via the simplified smart contract flow.

Top-ups support amounts starting from 1 gwei, but new validator creation requires at least 32 ETH.

Top-ups require 0x02 withdrawal credentials.

Standard Flow (Multi-step)

Applicable scenarios

  • You need fine-grained control over validator count, size per validator, and withdrawal credential type
  • You intend to manually broadcast deposit transactions

Endpoints (Ethereum)

Endpoints (SSV)

Flow

  1. Create a validator request by specifying the number of validators and optional parameters like amountPerValidator and withdrawalCredentialsType.
  2. Retrieve deposit data by polling the request status.
  3. Broadcast deposit transaction manually.

Behavior

  • If amountPerValidator > 32 ETH, the system defaults to 0x02 withdrawal credentials
  • You can explicitly set withdrawalCredentialsType to 0x01 or 0x02. For 0x02 withdrawal credentials, the standard flow ends after deposit data retrieval. Deposit broadcasting and validator activation are not supported for 0x02 in this flow (both in Ethereum and SSV).
  • The status response includes validator size, credential type, and depositData[]
  • SSV flow supports both 0x01 and 0x02 credentials, but for 0x02, it only supports data generation — not broadcasting or activation.

Flow Example

1. Create Validator Request

Send a POST request to /api/v1/eth/staking/direct/nodes-request/create.

Example request:

curl --request POST \
  --url https://api.p2p.org/api/v1/eth/staking/direct/nodes-request/create \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <token>' \
  --data '{
    "id": "f79ad9fe-6cfc-4480-ba80-683c26914971",
    "type": "REGULAR",
    "validatorsCount": 2,
    "withdrawalAddress": "0x368F823e4df...bf8e4a21",
    "controllerAddress": "0x368F823e4df...bf8e4a21",
    "feeRecipientAddress": "0x368F823e4df...bf8e4a21",
    "nodesOptions": {
      "location": "any"
    }
  }'

Example response:

{
  "error": null,
  "result": true
}

2. Poll Status to Get Deposit Data

Send a GET request to /api/v1/eth/staking/direct/nodes-request/status/{id}.

Example request:

curl --request GET \
  --url https://api.p2p.org/api/v1/eth/staking/direct/nodes-request/status/f79ad9fe-6cfc-4480-ba80-683c26914971 \
  --header 'Authorization: Bearer <token>'

Example response:

{
  "error": null,
  "result": {
    "status": "ready",
    "amountPerValidator": "32000000000",
    "withdrawalCredentialsType": "0x01",
    "depositData": [
      {
        "pubkey": "0xa6ecac19d...",
        "signature": "0xb2a...",
        "withdrawalCredentials": "0100000000...bf8e4a21",
        "amount": 32000000000,
        "depositDataRoot": "0x75348c59...",
        "depositMessageRoot": "0x3179df...",
        "forkVersion": "10000910",
        "eth2NetworkName": "hoodi",
        "depositCliVersion": "2.7.0"
      },
      {
        "pubkey": "0xb63e7e5c32f...",
        "signature": "0xa0b4...",
        "withdrawalCredentials": "0100000000...bf8e4a21",
        "amount": 32000000000,
        "depositDataRoot": "0x5177528...",
        "depositMessageRoot": "0x25c35250...",
        "forkVersion": "10000910",
        "eth2NetworkName": "hoodi",
        "depositCliVersion": "2.7.0"
      }
    ]
  }
}
  • amountPerValidator — amount in GWEI. (32 ETH = 32_000_000_000 GWEI)

3. Broadcast Deposit Transaction

The following broadcast example is only applicable for validators with 0x01 withdrawal credentials.

Send a POST request to /api/v1/eth/staking/direct/tx/deposit.

Example request:

curl --request POST \
  --url https://api.p2p.org/api/v1/eth/staking/direct/tx/deposit \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <token>' \
  --data '{
    "withdrawalAddress": "0x368F823e4df...689917bf8e4a21",
    "depositData": [
      {
        "pubkey": "0xa6ecac19d...",
        "signature": "0xb2a...",
        "withdrawalCredentials": "0100000000...bf8e4a21",
        "amount": 32000000000,
        "depositMessageRoot": "0x3179df...",
        "depositDataRoot": "0x75348c59...",
        "forkVersion": "10000910",
        "eth2NetworkName": "hoodi",
        "depositCliVersion": "2.7.0"
      },
      {
        "pubkey": "0xb63e7e5c32f...",
        "signature": "0xa0b4...",
        "withdrawalCredentials": "0100000000...bf8e4a21",
        "amount": 32000000000,
        "depositMessageRoot": "0x25c35250...",
        "depositDataRoot": "0x5177528...",
        "forkVersion": "10000910",
        "eth2NetworkName": "hoodi",
        "depositCliVersion": "2.7.0"
      }
    ]
}'

Example response:

{
  "error": null,
  "result": {
    "serializeTx": "0x02f904...",
    "value": "64000000000000000000",
    "chainId": 560048,
    "type": 2,
    "maxFeePerGas": "2221240288",
    "maxPriorityFeePerGas": "79693564"
  }
}

Legacy SSV Note

Deposit broadcasting is not supported for 0x02 credentials via legacy SSV flow.

Only deposit data generation is available at this time.


Simplified Flow (via Smart Contract 3.1)

This flow applies only to Ethereum Direct staking via smart contract 3.1.

The example below describes a legacy SSV flow and will be updated once SSV 3.1 integration is available.

Applicable scenarios

  • You want to minimize steps and create a validator using a prebuilt transaction
  • You intend to use 0x02 withdrawal credentials and avoid manual deposit data handling

Endpoints

Flow

  1. Call the endpoint with query parameters: amount, withdrawalAddress, controllerAddress, feeRecipientAddress
  2. Receive a fully constructed transaction ready for signing and broadcasting.

This flow is supported for Ethereum Direct staking only and applies exclusively to validators using 0x02 withdrawal credentials.

Flow Example

1. Get Prebuilt Transaction (Ethereum Direct 3.1)

Example request:

curl --request GET \
  --url "https://api.p2p.org/api/v1/eth/staking/direct/p2p/deposit?amount=32&withdrawalAddress=0x8eB2277e54A2A7db9b7c0D9FA9B78dcB35f74866&controllerAddress=0x8eB2277e54A2A7db9b7c0D9FA9B78dcB35f74866&feeRecipientAddress=0x3202B5B0602274197CcB22B8c02CEC9539990c7c" \
  --header 'Authorization: Bearer <token>'
  

Example response:

{
    "error": null,
    "result": {
        "serializeTx": "0x02f9025701808312c36c850727b59762839896809423be839a14cec3d6d716d904f09368bbf9c750eb8903782dace9d9000000b90224a49b131b0200000000000000000000008eb2277e54a2a7db9b7c0d9fa9b78dcb35f74866000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000003fcd8d9acac042095dfba53f4c40c74d19e2e9d9000000000000000000000000000000000000000000000000000000000000251c0000000000000000000000003202b5b0602274197ccb22b8c02cec9539990c7c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000e86b78000ed3b7b820ac6a39b46602356daee0602180fefa69a94774c3209a9f5ad1df42aa2c426b1494a39afc4ee79a528dca95807f5a1b518c8fb0a4df1be92b8a8b6e5f4fccf35f3975f9cf64aa92f4896f2553a5ba9fb0b13cabb25f21a2881f027d9703bfa57f45a514fff7bcc1b8020c2a3ab0581cfbb748dfe612a14acef40bc61de068cf3117e8227ab1315e0302f97e17d5b8948aa8071645b27ad74f669783d363fbc7e9dd58c01e535aff418b2fdbff56a55212e25efbc6b963bb048d98ecc1d03020f890bbf7518fd2463afb82ffdf452863268bdae4eae18d6108dce9ef803669f466000000000000000000000000000000000000000000000000c0",
        "to": "0x23BE839a14cEc3D6D716D904f09368Bbf9c750eb",
        "gasLimit": "10000000",
        "data": "0xa49b131b0200000000000000000000008eb2277e54a2a7db9b7c0d9fa9b78dcb35f74866000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000003fcd8d9acac042095dfba53f4c40c74d19e2e9d9000000000000000000000000000000000000000000000000000000000000251c0000000000000000000000003202b5b0602274197ccb22b8c02cec9539990c7c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000e86b78000ed3b7b820ac6a39b46602356daee0602180fefa69a94774c3209a9f5ad1df42aa2c426b1494a39afc4ee79a528dca95807f5a1b518c8fb0a4df1be92b8a8b6e5f4fccf35f3975f9cf64aa92f4896f2553a5ba9fb0b13cabb25f21a2881f027d9703bfa57f45a514fff7bcc1b8020c2a3ab0581cfbb748dfe612a14acef40bc61de068cf3117e8227ab1315e0302f97e17d5b8948aa8071645b27ad74f669783d363fbc7e9dd58c01e535aff418b2fdbff56a55212e25efbc6b963bb048d98ecc1d03020f890bbf7518fd2463afb82ffdf452863268bdae4eae18d6108dce9ef803669f466000000000000000000000000000000000000000000000000",
        "value": "64000000000000000000",
        "chainId": 1,
        "type": 2,
        "maxFeePerGas": "30730983266",
        "maxPriorityFeePerGas": "1229676"
    }
}

amount is in ETH (e.g., "32")

2. Sign and Broadcast transaction

Use the following example of the code to sign and broadcast the withdrawal transaction.

require("dotenv").config();
const ethers = require("ethers");

async function signAndBroadcast() {
  console.log("Started");

  // Enter the serialized transaction
  const rawTransaction = process.env.RAW_TRANSACTION;

  // Enter the private key of the address used to transfer the stake amount
  const privateKey = process.env.PRIVATE_KEY;

  // Enter the selected RPC URL
  const rpcURL = process.env.RPC_URL;

  // Initialize the provider using the RPC URL
  const provider = new ethers.providers.JsonRpcProvider(rpcURL);

  // Initialize a new Wallet instance
  const wallet = new ethers.Wallet(privateKey, provider);

  // Parse the raw transaction
  const tx = ethers.utils.parseTransaction(rawTransaction);

  tx.nonce = await provider.getTransactionCount(wallet.address);

  // Enter the max fee per gas and prirorty fee
  tx.maxFeePerGas = ethers.utils.parseUnits(
    process.env.MAX_FEE_PER_GAS_IN_GWEI,
    "gwei"
  );
  tx.maxPriorityFeePerGas = ethers.utils.parseUnits(
    process.env.MAX_PRIORITY_FEE_IN_GWEI,
    "gwei"
  );

  // Sign the transaction
  const signedTransaction = await wallet.signTransaction(tx);

  // Send the signed transaction
  const transactionResponse = await provider.sendTransaction(signedTransaction);

  return transactionResponse;
}

signAndBroadcast()
  .then((transactionResponse) => {
    console.log(
      "Transaction broadcasted, transaction hash:",
      transactionResponse.hash
    );
  })
  .catch((error) => {
    console.error("Error:", error);
  })
  .finally(() => {
    console.log("Finished");
  });