Ethereum Staking API

1 Stake

Staking ETH through the InfStones Safe Stake SDK involves a straightforward process: users stake a multiple of 32 ETH and are provided with an activity ID. This activity ID can be utilized to access the raw transaction which users can then sign and broadcast to complete the staking process seamlessly.

1.1 Stake

We charge a monthly maintenance fee of $19 for each Ethereum validator. To cover this monthly validator maintenance fee, users are required to add a credit card or reload their account credit on our platform.

Through stake API, users can stake ETH with their preferred payment method and get the activity ID.

Request

curl -X 'POST' \
  'https://stakingapi.infstones.com/ethereum/stake' \
  -H 'x-api-key: <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "wallet": "<your_ethereum_wallet_address>",
  "amount": "<stake_amount>", 
  "staking_type": "<staking_type>",
  "withdrawal_credentials": "<your_withdrawal_credential_address>"
}'
📘

Staking Type

  • Optional: <staking_type> can be omitted.

  • Default (no staking_type):

    <stake_amount> must be a multiple of 32 ETH. Each 32 ETH creates one validator.

  • Compounding (staking_type = "compounding"):

    As introduced in the Ethereum Pectra upgrade (EIP-7251), a validator can accept between 32 ETH and 2,048 ETH in a single instance. Rewards are automatically compounded, and only one validator will be generated even if the stake exceeds 32 ETH.

Example

Default Mode (multiple validators)

curl -X 'POST' \
  'https://stakingapi.infstones.com/ethereum/stake' \
  -H 'x-api-key: <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "wallet": "<your_ethereum_wallet_address>",
  "amount": "64", // Creates 2 validators 
  "withdrawal_credentials": "<your_withdrawal_credential_address>"
}'

Compounding Mode (single validator with >32 ETH)

curl -X 'POST' \
  'https://stakingapi.infstones.com/ethereum/stake' \
  -H 'x-api-key: <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "wallet": "<your_ethereum_wallet_address>",
  "amount": "64",  // Creates 1 validator with 64 ETH
  "staking_type": "compounding",
  "withdrawal_credentials": "<your_withdrawal_credential_address>"
}'

Response

{
  "data": "<activity_id>" // "3c3fb988-6dbb-475c-ac8c-185023a69060"
}
📘

<activity_id> is a UUID string, for example: "3c3fb988-6dbb-475c-ac8c-185023a69060".

1.2 Get Raw Transaction

Open GraphQL Playground, users can retrieve the raw transaction associated with the activity ID from 1.1 Stake.

Step 1: Input your access token in the Headers section.

Step 2: Input the activity ID and run the command, and you will get the response with raw_tx.

Request

{
  get_activity(id:"3c3fb988-6dbb-475c-ac8c-185023a69060") {
    activity {
      wallet
      amount
      raw_tx
      activity_status
    }
  }
}

Response

{
  "data": {
    "get_activity": {
      "activity": [
        {
          "wallet": "0xf404571702cf424174a3badaba7906b6651a8afe",
          "amount": "96",
          "raw_tx": "{\"from_address\":\"0x5a1f4d3975749719ef98716e0afb26fbc2216cb4\",\"to_address\":\"0x728bD584d0e35e42982a58dBa6968ff800b1c467\",\"data_list\":[{\"value\":\"0x3782dace9d9000000\",\"data\":\"0xc82655b700000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000060b018af0963dd539cf7b26b650ea2120e169dde93ed464ac1089123f2e223d76977e9606044bbdd8321b0ed48743e26d3a0adc67f84e1dfff76ea2135e2681aa4ac0c4fa753532727c378b2cfed9ee9403facdf16377a4bb67e4a41805214c19700000000000000000000000000000000000000000000000000000000000000200100000000000000000000005a1f4d3975749719ef98716e0afb26fbc2216cb400000000000000000000000000000000000000000000000000000000000000c0abdc89203c7b05df43851b6d6b25df23f3466fd51d1660ccb9adec5dbefa2fbdcd21f4471d54f3a74d5bf81e27dfc03603dbda621cc72aafac66b6e5048083b0ee0784c95efaf174a64e24c48735c57b1ce8b3e8726e0a7305dc658f46bf4135814d859c004d5670a2d794eb298309af6592cd0444cb2fbe78fe0b4065e351dd46a8a86f407c4dbdbe78e4a67456576812f3019f59602ddc81b2ecbb00eefba9a3498dd43438a3c94c42113b332f5b9c499e76a37ad30a8952357d4b223934f60000000000000000000000000000000000000000000000000000000000000002b52c7efcab609d0426c29f231745ef1e1e226fb5cc1edcf823fe4542191a5b039e8a6d8b961494ffdf8bfe25d57b7e56d7c5060044809435e054c8539b846b0a\"},{\"value\":\"0x1bc16d674ec800000\",\"data\":\"0xc82655b7000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000030873938eaae9a07c755a5ef9a7369799e3237765966d17ab192a34f237e0ccee48450a847e53068c6495a6e3ca029dc210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000005a1f4d3975749719ef98716e0afb26fbc2216cb40000000000000000000000000000000000000000000000000000000000000060b767a3ecfd3e7ed1edf1df0d4c22b23182abbc009ea10cbafde62022f00d4ae1aa49cafbd91df3818ed8a2c4eb2b50400296eb0a3c97996a704b172071d93b6c67596bd9b06aa8edf5148dc6354d66bf7bd0494621aea0b1a95dab076ba8b2d80000000000000000000000000000000000000000000000000000000000000001d90d636ca3b8d4d77ecd9f2183ecc0c9817e3ebe8852684ed4b485b7d8974c9d\"}],\"gas_limit\":\"\"}",
          "activity_status": "completed"
        }
      ]
    }
  }
}
📘

The activity status completed means the transaction details are fully prepared and ready for the next step Sign Transaction and Broadcast.

1.3 Sign Transaction and Broadcast

Users can sign the raw transaction using their own wallet's private key and broadcast it to complete the staking process.

Follow the instructions to get your Ethereum Fast API endpoint.

import Web3 from "web3"

// Step 1: Retrieve raw tx from Infstones activity
const rawEscapedJson = `{\"from_address\":\"0x5a1f4d3975749719ef98716e0afb26fbc2216cb4\",\"to_address\":\"0x728bD584d0e35e42982a58dBa6968ff800b1c467\",\"data_list\":[{\"value\":\"0x3782dace9d9000000\",\"data\":\"0xc82655b700000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000060b018af0963dd539cf7b26b650ea2120e169dde93ed464ac1089123f2e223d76977e9606044bbdd8321b0ed48743e26d3a0adc67f84e1dfff76ea2135e2681aa4ac0c4fa753532727c378b2cfed9ee9403facdf16377a4bb67e4a41805214c19700000000000000000000000000000000000000000000000000000000000000200100000000000000000000005a1f4d3975749719ef98716e0afb26fbc2216cb400000000000000000000000000000000000000000000000000000000000000c0abdc89203c7b05df43851b6d6b25df23f3466fd51d1660ccb9adec5dbefa2fbdcd21f4471d54f3a74d5bf81e27dfc03603dbda621cc72aafac66b6e5048083b0ee0784c95efaf174a64e24c48735c57b1ce8b3e8726e0a7305dc658f46bf4135814d859c004d5670a2d794eb298309af6592cd0444cb2fbe78fe0b4065e351dd46a8a86f407c4dbdbe78e4a67456576812f3019f59602ddc81b2ecbb00eefba9a3498dd43438a3c94c42113b332f5b9c499e76a37ad30a8952357d4b223934f60000000000000000000000000000000000000000000000000000000000000002b52c7efcab609d0426c29f231745ef1e1e226fb5cc1edcf823fe4542191a5b039e8a6d8b961494ffdf8bfe25d57b7e56d7c5060044809435e054c8539b846b0a\"},{\"value\":\"0x1bc16d674ec800000\",\"data\":\"0xc82655b7000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000030873938eaae9a07c755a5ef9a7369799e3237765966d17ab192a34f237e0ccee48450a847e53068c6495a6e3ca029dc210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000005a1f4d3975749719ef98716e0afb26fbc2216cb40000000000000000000000000000000000000000000000000000000000000060b767a3ecfd3e7ed1edf1df0d4c22b23182abbc009ea10cbafde62022f00d4ae1aa49cafbd91df3818ed8a2c4eb2b50400296eb0a3c97996a704b172071d93b6c67596bd9b06aa8edf5148dc6354d66bf7bd0494621aea0b1a95dab076ba8b2d80000000000000000000000000000000000000000000000000000000000000001d90d636ca3b8d4d77ecd9f2183ecc0c9817e3ebe8852684ed4b485b7d8974c9d\"}],\"gas_limit\":\"\"}`;

// Step 2: Config your Ethereum private key and RPC
const PRIVATE_KEY = "<your_ethereum_wallet_private_key>"; // Replace this with your Ethereum wallet private key
const RPC_URL = "<ethereum_endpoint>"; // Replace this with your Ethereum Fast API endpoint

const web3 = new Web3(RPC_URL);
const unescaped = JSON.parse(rawEscapedJson);
const jsonInput = typeof unescaped === "string" ? JSON.parse(unescaped) : unescaped;


async function sendBatchTransactions() {
  try {
    const walletAddress = jsonInput.from_address.toLowerCase();
    const toAddress = jsonInput.to_address;
    let txCount = await web3.eth.getTransactionCount(walletAddress, "pending");

    for (let i = 0; i < jsonInput.data_list.length; i++) {
      const item = jsonInput.data_list[i];
      const nonce = web3.utils.toHex(txCount + BigInt(i));

      const rawTx = {
        to: toAddress,
        nonce: nonce,
        value: item.value,
        data: item.data,
        gasPrice: await web3.eth.getGasPrice(),
      };
      rawTx.gas = await web3.eth.estimateGas(rawTx);

      const signedTx = await web3.eth.accounts.signTransaction(rawTx, PRIVATE_KEY);
      const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
      console.log(`✅ Tx ${i + 1} successful. Hash: ${receipt.transactionHash}`);
    }
  } catch (err) {
    console.error("❌ Error sending transaction batch:", err);
  }
}

sendBatchTransactions();

Follow the instructions to sign Ethereum transaction with Ledger.

2 Topup

The Topup API allows adding additional ETH to an existing validator.

⚠️

Only compounding validators are supported (i.e., validators whose withdrawal_credentials start with 0x02).

Request

curl -X 'POST' \
  'https://stakingapi.infstones.com/ethereum/topup' \
  -H 'x-api-key: <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "wallet": "<your_ethereum_wallet_address>",
    "amount": "<topup_amount>",
    "pubkey": "<validator_pubkey>"
}'
📘

Notes

  • <topup_amount> must be a positive ETH value.
  • <pubkey> must belong to an existing compounding validator (with withdrawal_credentials starting with 0x02).
  • The validator balance after top-up cannot exceed the effective balance restricted by InfStones (1,920 ETH).

Response

{
    "data": "<activity_id>"
}

After submitting a Topup request, the flow continues the same way as for the Stake request:

  1. Get Raw Transaction (1.2)
    • Use the returned activityId from the Topup request to query the raw transaction via the get_activity GraphQL API.
    • The response contains the unsigned transaction data.
  2. Sign and Broadcast (1.3)
    • Use your own Ethereum wallet private key to sign the raw transaction.
    • Broadcast the signed transaction to the Ethereum network through your preferred RPC endpoint.
    • Once confirmed on-chain, the validator’s balance will be updated.

3 Withdrawal

The Withdrawal API allows users to withdraw ETH from an existing compounding validator. The workflow is the same as Stake and Topup, and requires completing the full transaction signing and broadcasting steps.

⚠️

Only compounding validators are supported (i.e., validators whose withdrawal_credentials start with 0x02).

Request

curl -X 'POST' \
  'https://stakingapi.infstones.com/ethereum/withdrawal' \
  -H 'x-api-key: <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "wallet": "<your_ethereum_wallet_address>",
    "amount": "<withdrawal_amount>",
    "pubkey": "<validator_pubkey>"
}'
📘

Notes

  • <withdrawal_amount> must be a positive ETH value.
  • <pubkey> must belong to an existing compounding validator (with withdrawal_credentials starting with 0x02).
  • The ETH withdrawn will always be sent to the validator’s withdrawal_credentials (0x02 execution address). The wallet field in the request identifies the caller but does not override the withdrawal target.

Response

{
    "data": "<activity_id>"
}

After submitting a withdrawal request, the next step is to use the returned <activity_id> to get raw transaction (1.2).

Then, follow 3.1 Sign Transaction and Broadcast to sign and send the withdrawal transaction using the dedicated script.

3.1 Sign Transaction and Broadcast

For Withdrawal, the returned raw_tx structure may differ from Stake/Topup (which commonly provide a data_list for batched calls). Typically, Withdrawal returns a single-transaction payload (e.g., including data and value directly, without data_list). Use the following script to sign and broadcast a single withdrawal transaction.

import Web3 from "web3"

// Step 1: Retrieve raw tx from Infstones activity
const rawEscapedJson = `{\"from_address\":\"0x5a1f4d3975749719ef98716e0afb26fbc2216cb4\",\"to_address\":\"0x00000961Ef480Eb55e80D19ad83579A64c007002\",\"value\":\"0x1\",\"data\":\"0xb16d7e66358c2a1b3b70fdc1ecfa4e75966f9edff4c15e00341d722e490a6a06f63bfcd8426c676c1ff351577163d9fe000000003b9aca00\"}`

// Step 2: Config your Ethereum private key and RPC
const PRIVATE_KEY = "<your_ethereum_wallet_private_key>"; // Replace this with your Ethereum wallet private key
const RPC_URL = "<ethereum_endpoint>"; // Replace this with your Ethereum Fast API endpoint

const web3 = new Web3(RPC_URL);
const unescaped = JSON.parse(rawEscapedJson);
const jsonInput = typeof unescaped === "string" ? JSON.parse(unescaped) : unescaped;

async function sendTransactions() {
  try {
    const walletAddress = jsonInput.from_address.toLowerCase();
    const toAddress = jsonInput.to_address;
    let txCount = await web3.eth.getTransactionCount(walletAddress, "pending");    
    const nonce = web3.utils.toHex(txCount);

    const rawTx = {
        to: toAddress,
        nonce: nonce,
        value: jsonInput.value,
        data: jsonInput.data,
        gasPrice: await web3.eth.getGasPrice(),
    };
    rawTx.gas = await web3.eth.estimateGas(rawTx);

    const signedTx = await web3.eth.accounts.signTransaction(rawTx, PRIVATE_KEY);
    const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
    console.log(`✅ Tx is successful. Hash: ${receipt.transactionHash}`);
    
  } catch (err) {
    console.error("❌ Error sending transaction batch:", err);
  }
}

sendTransactions();

4 Unstake

Unstaking ETH through the InfStones Safe Stake SDK involves a straightforward process: users sign a message and are provided with a signature. This signature can be utilized to unstake a multiple of 32 ETH and generate an activity ID which can be used to retrieve the activity status.

4.1 Sign Message

Users can sign the message using their own wallet's private key and get the signature.

Request

import Web3 from "web3"
import pkg from 'js-sha3'
const { keccak256 } = pkg

const web3 = new Web3("<ethereum_mainnet_endpoint>") // Replace this with your Ethereum Fast API endpoint
const wallet = { privateKey: "<your_ethereum_wallet_private_key>" }

try {
    const message = {validators: ["<ethereum_validator_address_1>","<ethereum_validator_address_2>"]}
    const msgStr = JSON.stringify(message);
    const hash = keccak256(msgStr);
    const captcha = "Captcha:" + hash
    let res = await web3.eth.accounts.sign(captcha, "0x"+wallet.privateKey)
    console.log(res.signature)
} catch (error) {
    console.log(error)
}
📘

message should include vallidator address information.

Response

0xb51cef2fc78552464c4632c0bf2f9cfd6c583daedbb4627bc65ff35404cabf9254191d7f7cf6674b9f8a8a31519190a345460a13a8577f2b819f106958320df31c

Follow the instructions to sign Ethereum message with Ledger.

4.2 Unstake

Through unstake API, users can unstake a multiple of 32 ETH using the signature from 4.1 Sign Message and get the activity ID.

Request

curl -X 'POST' \
  'https://stakingapi.infstones.com/ethereum/unstake' \
  -H 'x-api-key: <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
  "wallet": "<your_ethereum_wallet_address>",
  "amount": "<unstake_amount>",
  "data": {
    "message": "{\"validators\":[\"<ethereum_validator_address_1>\",\"<ethereum_validator_address_2>\"]}",
    "signature": "0xb51cef2fc78552464c4632c0bf2f9cfd6c583daedbb4627bc65ff35404cabf9254191d7f7cf6674b9f8a8a31519190a345460a13a8577f2b819f106958320df31c"
  }
}'
📘

<unstake_amount> should be a multiple of 32.

Response

{
  "data": "<activity_id>" // "0d24dcd9-d837-4095-8ad5-553ef1ddfe53"
}

4.3 Get Activity Status

Open GraphQL Playground, users can retrieve the activity status with the activity ID from 4.2 Unstake.

Request

{
  get_activity(id:"0d24dcd9-d837-4095-8ad5-553ef1ddfe53") {
    activity {
      activity_status
    }
  }
}

Response

{
  "data": {
    "get_activity": {
      "activity": [
        {
          "activity_status": "completed"
        }
      ]
    }
  }
}
📘

The activity status completed means that the unstaking operation has been fully executed and confirmed.

5 EL Reward Distribution

EL Rewards are automatically calculated and settled by InfStones.

On the 1st of each month, all accumulated rewards from the previous month will be distributed directly to the user’s staked wallet address.

Users do not need to send a separate API request to claim rewards.