You can stake to either pre-Pectra (0x01) or post-Pectra (0x02) validators by selecting the appropriate withdrawal credential type.
Minimum stake: 32 ETH
Maximum (post-Pectra only): 2048 ETH
Ensure your external address has enough ETH to cover the stake plus gas fees.
The example below illustrates how to stake from an external address.
Dedicated ETH Staking can take up to 5 minutes to generate a staking transaction,
as it involves provisioning dedicated backend infrastructure.
Until it’s ready, the Transaction field in the StakeOperation will remain empty.
Copy
Ask AI
import { Coinbase, ExternalAddress, StakeOptionsMode } from "@coinbase/coinbase-sdk";// Create a new external address on the ethereum-hoodi testnet network.let address = new ExternalAddress( Coinbase.networks.EthereumHoodi, "YOUR_WALLET_ADDRESS",);// Find out how much ETH is available to stake.let stakeableBalance = await address.stakeableBalance( Coinbase.assets.Eth, StakeOptionsMode.NATIVE,);console.log("Stakeable balance: %s", stakeableBalance)// Build a stake operation for 100 ETH.// Set withdrawal_credential_type to:// - "0x01" for pre-Pectra// - "0x02" for post-Pectra (required for balances > 32 ETH)let stakingOperation = await address.buildStakeOperation( 100, Coinbase.assets.Eth, StakeOptionsMode.NATIVE, {"withdrawal_credential_type": "0x02"},);console.log("Staking Operation ID: %s", stakingOperation.getID())// Wait for staking infrastructure to be provisioned and transaction created.await stakingOperation.wait();
Post-Pectra validators can now be unstaked directly from the execution layer using the withdrawal address.
This bypasses the consensus-layer exit process entirely.
Supports both:
Partial withdrawals: Withdraw a portion of a validator’s balance
Full exits: Exit the validator completely and withdraw all funds
import { Coinbase, ExternalAddress, StakeOptionsMode, ExecutionLayerWithdrawalOptionsBuilder,} from "@coinbase/coinbase-sdk";import Decimal from "decimal.js";// Create a new external address on the ethereum-hoodi testnet network// corresponding to the withdrawal address of the validators you want// to partially withdraw from.let withdrawAddr = new ExternalAddress( Coinbase.networks.EthereumHoodi, "YOUR_WITHDRAWAL_ADDRESS",);// Configure partial withdrawals for two post-Pectra validators.let buildr = new ExecutionLayerWithdrawalOptionsBuilder(withdrawAddr.getNetworkId());buildr.addValidatorWithdrawal("YOUR_VALIDATOR_PUBKEY_1", new Decimal("1"));buildr.addValidatorWithdrawal("YOUR_VALIDATOR_PUBKEY_2", new Decimal("2"));let options = await buildr.build();let stakingOperation = await withdrawAddr.buildUnstakeOperation( 0, // Amount here doesn't matter. Coinbase.assets.Eth, StakeOptionsMode.NATIVE, options,);console.log("Staking Operation ID: %s", stakingOperation.getID())// Wait for the partial withdrawal transactions to be built.await stakingOperation.wait();
import { Coinbase, ExternalAddress, StakeOptionsMode, ExecutionLayerWithdrawalOptionsBuilder,} from "@coinbase/coinbase-sdk";import Decimal from "decimal.js";// Create a new external address on the ethereum-hoodi testnet network// corresponding to the withdrawal address of the validators you want// to fully exit.let withdrawAddr = new ExternalAddress( Coinbase.networks.EthereumHoodi, "YOUR_WITHDRAWAL_ADDRESS",);// Build a full exit staking operation to exit 2 different post Pectra validators.let buildr = new ExecutionLayerWithdrawalOptionsBuilder(withdrawAddr.getNetworkId());buildr.addValidatorWithdrawal("YOUR_VALIDATOR_PUBKEY_1", new Decimal("0"));buildr.addValidatorWithdrawal("YOUR_VALIDATOR_PUBKEY_2", new Decimal("0"));let options = await buildr.build();let stakingOperation = await withdrawAddr.buildUnstakeOperation( 0, // Amount here doesn't matter. Coinbase.assets.Eth, StakeOptionsMode.NATIVE, options,);console.log("Staking Operation ID: %s", stakingOperation.getID())// Wait for the full exit transactions to be built.await stakingOperation.wait();
There are two options to build the coinbase managed unstake operation.
By Amount
Coinbase managed unstake by amount currently only supports selection of pre Pectra validators for unstaking.
For 0x01 validators, this amount should be in multiples of 32. If amount = 64 ETH, we pick 2 0x01 validators and exit them.
This behind the scenes will identify validators to be exited, generate a voluntary exit message per validator, sign it with the
validator’s private key and broadcast them for you.
Copy
Ask AI
import { Coinbase, ExternalAddress, StakeOptionsMode } from "@coinbase/coinbase-sdk";// Create a new external address on the ethereum-hoodi testnet network.let address = new ExternalAddress( Coinbase.networks.EthereumHoodi, "YOUR_WALLET_ADDRESS",);// To know how much ETH balance is available for unstaking, use `unstakeableBalance`.// Unstakeable balance depends on your CDP account validators, not your address.// It's surfaced on the address object for simplicity.// Set `withdrawal_credential_type` to 0x01 or 0x02 to query specific validators.// By default, it returns the unstakeable balance for 0x01 validators.let unstakeableBalance = await address.unstakeableBalance( Coinbase.assets.Eth, StakeOptionsMode.NATIVE, );console.log("Unstakeable balance: %s", unstakeableBalance)// Build unstake operation for amount = 32 ETH.let stakingOperation = await address.buildUnstakeOperation( 32, Coinbase.assets.Eth, StakeOptionsMode.NATIVE, {"immediate": "true"},);console.log("Staking Operation ID: %s", stakingOperation.getID())// Immediate native eth unstaking is completely handled by the API// with no user action needed.// Example of polling the unstake operation status until it reaches// a terminal state using the SDK.await stakingOperation.wait();
By Validator
We support unstaking of both pre & post Pectra validators by validator pub keys. The amount is ignored in this case.
Copy
Ask AI
import { Coinbase, ExternalAddress, StakeOptionsMode, ConsensusLayerExitOptionBuilder,} from "@coinbase/coinbase-sdk";// Create a new external address on the ethereum-hoodi testnet network.let address = new ExternalAddress( Coinbase.networks.EthereumHoodi, "YOUR_WALLET_ADDRESS",);let options: { [key: string]: string } = { immediate: "true" };const builder = new ConsensusLayerExitOptionBuilder();builder.addValidator("YOUR_VALIDATOR_PUBKEY_1");builder.addValidator("YOUR_VALIDATOR_PUBKEY_2");options = await builder.build(options);let stakingOperation = await address.buildUnstakeOperation( 0, // Amount here doesn't matter. "eth", StakeOptionsMode.NATIVE, options,);console.log("Staking Operation ID: %s", stakingOperation.getID())// Immediate native eth unstaking is completely handled by the API// with no user action needed.// Example of polling the unstake operation status until it reaches// a terminal state using the SDK.await stakingOperation.wait();
Once the unstake operation has completed successfully, congrats you’ve just exited a validator.
Refer to the View Validator Information section to monitor your validator status.
When it changes to WITHDRAWAL_COMPLETE, your funds should be available in the withdrawal_address set during staking.
There are 2 options to build the coinbase managed unstake operation.
By Amount
User managed unstake by amount currently only supports selection of pre Pectra validators for unstaking.
If you want to be able to unstake both pre & post Pectra validators, use the “Unstake by Validator” option.
For 0x01 validators this amount should be in multiples of 32. If amount = 64 ETH, we pick 2 0x01 validators and exit them.
This behind the scenes will identify validators to be exited, generate a voluntary exit message per validator, sign it with the
validator’s private key and broadcast them for you.
Copy
Ask AI
import { Coinbase, ExternalAddress, StakeOptionsMode } from "@coinbase/coinbase-sdk";// Create a new external address on the ethereum-hoodi testnet network.let address = new ExternalAddress( Coinbase.networks.EthereumHoodi, "YOUR_WALLET_ADDRESS",);// To know how much ETH balance is available for unstaking, use `unstakeableBalance`.// Unstakeable balance depends on your CDP account validators, not your address.// It's surfaced on the address object for simplicity.// Set `withdrawal_credential_type` to 0x01 or 0x02 to query specific validators.// By default, it returns the unstakeable balance for 0x01 validators.let unstakeableBalance = await address.unstakeableBalance( Coinbase.assets.Eth, StakeOptionsMode.NATIVE,);console.log("Unstakeable balance: %s", unstakeableBalance)// Build unstake operation for amount = 32 ETH.let stakingOperation = await address.buildUnstakeOperation( 32, Coinbase.assets.Eth, StakeOptionsMode.NATIVE,);console.log("Staking Operation ID: %s", stakingOperation.getID())// Native eth unstaking can take some time as we build the voluntary exit message// and have it signed by the validator.// Example of polling the unstake operation status until it reaches// a terminal state using the SDK.await stakingOperation.wait();
By Validator
We support unstaking of both pre & post Pectra validators by validator pub keys. The amount is ignored in this case.
Copy
Ask AI
import { Coinbase, ExternalAddress, StakeOptionsMode, ConsensusLayerExitOptionBuilder,} from "@coinbase/coinbase-sdk";// Create a new external address on the ethereum-hoodi testnet network.let address = new ExternalAddress( Coinbase.networks.EthereumHoodi, "YOUR_WALLET_ADDRESS",);const builder = new ConsensusLayerExitOptionBuilder();builder.addValidator("YOUR_VALIDATOR_PUBKEY_1");builder.addValidator("YOUR_VALIDATOR_PUBKEY_2");let options = await builder.build();let stakingOperation = await address.buildUnstakeOperation( 0, // Amount here doesn't matter. "eth", StakeOptionsMode.NATIVE, options,);console.log("Staking Operation ID: %s", stakingOperation.getID())// Native eth unstaking can take some time as we build the voluntary exit message// and have it signed by the validator.// Example of polling the unstake operation status until it reaches// a terminal state using the SDK.await stakingOperation.wait();
You can consolidate smaller pre-Pectra (0x01) validators into larger post-Pectra (0x02) validators, without manually unstaking and re-staking.
This reduces the number of active validators you manage and enables auto-compounding rewards.
Two modes:
Self-consolidation: Convert a validator from 0x01 → 0x02 by setting the same pubkey as both source and target.
Merge: Consolidate a single 0x01 validator under an existing 0x02 validator.
Copy
Ask AI
import { Coinbase, ExternalAddress } from "@coinbase/coinbase-sdk";// Create a new external address on the ethereum-hoodi testnet network.let withdrawAddr = new ExternalAddress( Coinbase.networks.EthereumHoodi, "YOUR_WALLET_ADDRESS",);// Build a validator consolidate operation.// To perform self consolidation, set the source and target validator public// keys to the same value. This converts existing 0x01 validators to 0x02.// To perform consolidation, set the source and target validator public keys// to different values. This consolidates existing 0x01 validators under an// existing 0x02 validator.let stakingOperation = await withdrawAddr.buildValidatorConsolidationOperation({ "source_validator_pubkey": "YOUR_SOURCE_VALIDATOR_PUBKEY", "target_validator_pubkey": "YOUR_TARGET_VALIDATOR_PUBKEY"});console.log("Staking Operation ID: %s", stakingOperation.getID())await stakingOperation.wait();
Validator top-ups allow you to add more ETH to an existing validator. This is useful for increasing the validator’s effective balance and rewards.
Copy
Ask AI
import { Coinbase, ExternalAddress, StakeOptionsMode } from "@coinbase/coinbase-sdk";// Create a new external address on the ethereum-hoodi testnet network.let address = new ExternalAddress( Coinbase.networks.EthereumHoodi, "YOUR_WALLET_ADDRESS",);// Build a top-up stake operation.// This is similar to a normal stake operation, but the amount is topped-up on an// existing validator provided by the `top_up_validator_pubkey` option instead of// creating a new validator.let stakingOperation = await address.buildStakeOperation( 2, // Amount to top-up. Coinbase.assets.Eth, StakeOptionsMode.NATIVE, {"top_up_validator_pubkey": "YOUR_VALIDATOR_PUBKEY"},);console.log("Staking Operation ID: %s", stakingOperation.getID())await stakingOperation.wait();
You can view historical staking rewards by validator address. This helps you track earnings over time, including USD-converted value and conversion rates.
Refer to the StakingReward docs for a full list of supported methods.
Look up staking rewards for a list of addresses.
Copy
Ask AI
import { Coinbase, StakingReward } from "@coinbase/coinbase-sdk";let now = new Date();let tenDaysAgo = new Date();tenDaysAgo.setDate(now.getDate() - 10);let rewards = await StakingReward.list( Coinbase.networks.EthereumMainnet, Coinbase.assets.Eth, ["VALIDATOR_ADDRESS1", "VALIDATOR_ADDRESS2"], tenDaysAgo.toISOString(), now.toISOString(),);// Loop through the rewards and print each staking rewardrewards.forEach(reward => console.log(reward.toString()));
Detailed information about the historical staking balances for given validator address, including bonded and unbonded stakes.
Bonded Stakes: The total amount of stake that is actively earning rewards to this address. Pending active stake is not included.
Unbonded Balance: This amount includes any ETH balance that is under the control of the wallet address but is not actively staked.
Refer to the StakingBalance docs for a full list of supported methods.
Look up staking balances for an address.
Copy
Ask AI
import { Coinbase, StakingBalance } from "@coinbase/coinbase-sdk";let now = new Date();let tenDaysAgo = new Date();tenDaysAgo.setDate(now.getDate() - 10);let stakingBalances = await StakingBalance.list( Coinbase.networks.EthereumMainnet, Coinbase.assets.Eth, "VALIDATOR_ADDRESS", tenDaysAgo.toISOString(), now.toISOString(),);// Loop through the historical staking balances and print each balancestakingBalances.forEach(stakingBalance => console.log(stakingBalance.toString()));
Detailed information is available for any validators that you’ve created. The validator status (i.e. provisioned, active, etc.) is available in the response and is printed to stdout in the example below.
The Validator object documentation is available here and the ListValidators documentation is available here
Copy
Ask AI
// Get the validators that you've provisioned for staking.const validators = await Validator.list(Coinbase.networks.EthereumHoodi, Coinbase.assets.Eth);// Loop through the validators and print each validatorvalidators.forEach(validator => { console.log(validator.toString());});
Your validators will be listed with their respective statuses.
A validator can have the following statuses, provided in the status field of the response:
Status
Description
Onchain State Equivalent
Action Required
Provisioning
Validator is being created by Coinbase
:no_entry_sign: (Coinbase Only Status)
Wait :hourglass_flowing_sand:
Provisioned
Validator has been created by Coinbase and is ready for a deposit
:no_entry_sign: (Coinbase Only Status)
Sign and broadcast the provided deposit transaction
Deposited
Deposit transaction has been signed, broadcasted, and finalized on the Ethereum network
:no_entry_sign: (Coinbase Only Status)
Wait :hourglass_flowing_sand:
Pending
Validator is in the activation queue. This means the Ethereum network has successfully executed the deposit transaction
pending_queued
Wait :hourglass_flowing_sand:
Active
Validator is active and earning rewards
active_ongoing
None
Exiting
Validator is in the exit queue. The validator is still earning rewards
active_exiting
Wait :hourglass_flowing_sand:
Exited
Validator is waiting to enter the withdrawal queue. This means the validator has exited the active set and rewards are no longer being earned.
exited_unslashed
Wait :hourglass_flowing_sand:
Withdrawal Available
Validator is in the withdrawal queue. The network will sweep available funds to the withdrawal_address on a predetermined schedule
withdrawal_possible
Wait :hourglass_flowing_sand:
Withdrawal Complete
Validator has completed its lifecycle. It no longer has any validating responsibilities and the available funds (rewards and initial stake) have been swept to the withdrawal_address
withdrawal_done
None
Unavailable
Validator was provisioned, but a deposit transaction was never broadcasted. Coinbase has spun down the provisioned validator
:no_entry_sign: (Coinbase Only Status)
None
Active Slashed
Validator has been slashed in a previous epoch. The validator is still in the active set, but rewards cannot be earned and a voluntary exit cannot be performed
active_slashed
Wait :hourglass_flowing_sand:
Exited Slashed
Validator has been slashed in a previous epoch. The validator has exited the active set
You can filter the list of validators to view all validators with a specific status.
Copy
Ask AI
// Show all your validators with an active status.const validators = await Validator.list( Coinbase.networks.EthereumHoodi, Coinbase.assets.Eth, ValidatorStatus.ACTIVE,);
Your validators will be listed only if the status is active.
Copy
Ask AI
Id: 0xa3fc791b5abb4b83fe0e9fe2f6bc5a2728f967c5e845dd353cfac6d9ed4677ad39aa32ee25a1dbdaad8248d71ee1e3a4, Status: activeId: 0xadc25472f45a72446d0b5f7b5ec5760db14b198a21a8b0ad40ec673365c54ba1688ad0913f171135a94d4ce1f0ee684f, Status: activeId: 0x8071b39b9cfaefc094aff22c76a30f41709ed18f00b36efd63c7c64c644b3482bdfad5018fa32246af1a6c96943c750c, Status: active
The example below broadcasts pre-signed voluntary exit messages surfaced during an unstake process. Ethereum validator exit messages are special transaction types which are pre-signed by the validator keys and must be broadcast directly to the consensus layer.
Copy
Ask AI
// For Hoodi, publicly available RPC URL's can be// found here https://chainlist.org/chain/560048stakingOperation.getSignedVoluntaryExitMessages().forEach(async signedVoluntaryExitMessage => { let resp = await axios.post("HOODI_RPC_URL/eth/v1/beacon/pool/voluntary_exits", signedVoluntaryExitMessage) console.log(resp.status);});
The example below signs and broadcasts transactions surfaced via the staking operation resource.
These are standard execution-layer EIP-1159 transactions and follow the normal Ethereum signing flow.
Copy
Ask AI
// Load your wallet's private key from which you initiated the above stake operation.const wallet = new ethers.Wallet("YOUR_WALLET_PRIVATE_KEY");// Sign the transactions within staking operation resource with your wallet.await stakingOperation.sign(wallet);// For Hoodi, publicly available RPC URL's can be// found here https://chainlist.org/chain/560048const provider = new ethers.JsonRpcProvider("HOODI_RPC_URL");// Broadcast each of the signed transactions to the network.stakingOperation.getTransactions().forEach(async tx => {let resp = await provider.broadcastTransaction(tx.getSignedPayload()!); console.log(resp);});