Coinbase Staking API support for Ethereum Pectra features is in Beta on the Ethereum Hoodi testnet. Try it out here and share feedback on Discord.
Dedicated ETH Staking must use increments of 32 ETH. You will be staking directly to ETH validators that the API will stand up on your behalf.See the quickstart to familiarize yourself with Coinbase Staking API and basic usage.
Currently, Dedicated ETH staking only supports addresses used with the Coinbase Staking API specifically.
Coinbase App addresses and Coinbase Prime addresses are not supported.
The external address model is an address model where the private keys are not managed by the Coinbase SDK. The developer would be responsible for “bringing their own wallet”. All signing operations must be completed off-platform.
To stake, ensure that the address contains an increment of 32 ETH plus additional ETH to cover transaction 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 to sign.When a user stakes to Dedicated ETH, dedicated infrastructure is created on the backend, leading to the longer wait times.The Transaction in the StakeOperation will be empty until there is a transaction to sign.
Typescript
Go
Copy
Ask AI
import { Coinbase, ExternalAddress, StakeOptionsMode } from "@coinbase/coinbase-sdk";// Create a new external address on the ethereum-holesky testnet network.let address = new ExternalAddress(Coinbase.networks.EthereumHolesky, "YOUR_WALLET_ADDRESS");// Find out how much ETH is available to stake.let stakeableBalance = await address.stakeableBalance(Coinbase.assets.Eth, StakeOptionsMode.NATIVE);// Build a stake operation for an amount <= stakeableBalance, and in multiples of 32. In this case, 64 ETH.// This step will trigger the provisioning of validator infrastructure for your dedicated usage.// Infrastructure setup times vary with the number of validators. For 30 validators (960 ETH), it can take up to 5 minutes.let stakingOperation = await address.buildStakeOperation(64, Coinbase.assets.Eth, StakeOptionsMode.NATIVE);// Native ETH staking involves setting up infrastructure, which can take time.// Example of polling the stake operation status until it reaches a terminal state using the SDK.await stakingOperation.wait();
Once the stake operation has been built, relay the transactions to your end-user for signing and broadcasting. Refer to the Signing and Broadcasting Transactions section for an example using Ethers.js.
Unstaking on native ETH requires a voluntary exit message to be signed by the validator and submitted to the network to initiate the unstaking process. For external addresses, this can be done two ways:
Coinbase managed unstake(recommended) : You can request Coinbase to begin unstaking your ETH. Coinbase will build a signed voluntary exit message and broadcast it to the network on your behalf.
import { Coinbase, ExternalAddress } from "@coinbase/coinbase-sdk";// Create a new external address on the ethereum-holesky testnet network.let address = new ExternalAddress(Coinbase.networks.EthereumHolesky, "YOUR_WALLET_ADDRESS");// To know how much ETH balance across all your validators is available for staking// you can use the `unstakeableBalance` method as shown below.// Note: For Dedicated ETH Staking, the unstakeable balance depends on the validators owned by the CDP account,// not your address. We surface the unstakeable balance on the address object for simplicity.let unstakeableBalance = await address.unstakeableBalance(Coinbase.assets.Eth, StakeOptionsMode.NATIVE);// Build an unstake operation for an amount <= unstakeableBalance, and in multiples of 32. In this case, 32 ETH.// 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.let stakingOperation = await address.buildUnstakeOperation(32, Coinbase.assets.Eth, StakeOptionsMode.NATIVE, {"immediate": "true"});// 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.
import { Coinbase, ExternalAddress } from "@coinbase/coinbase-sdk";// Create a new external address on the ethereum-holesky testnet network.let address = new ExternalAddress(Coinbase.networks.EthereumHolesky, "YOUR_WALLET_ADDRESS");// To know how much ETH balance across all your validators is available for staking// you can use the `unstakeableBalance` method as shown below.// Note: For Dedicated ETH Staking, the unstakeable balance depends on the validators owned by the CDP account,// not your address. We surface the unstakeable balance on the address object for simplicity.let unstakeableBalance = await address.unstakeableBalance(Coinbase.assets.Eth, StakeOptionsMode.NATIVE);// Build an unstake operation for an amount <= unstakeableBalance, and in multiples of 32. In this case, 32 ETH.// This behind the scenes will identify validators to be exited, generate a voluntary exit message per validator,// and relay them back to be broadcasted appropriately to the Ethereum network.let stakingOperation = await address.buildUnstakeOperation(32, Coinbase.assets.Eth, StakeOptionsMode.NATIVE);// 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();
After unstaking, voluntary exit messages can be read and stored on your end and broadcasted to the network whenever you want to initiate the unstaking process. Refer to the Broadcasting Exit Messages section for an example.
The wallet address model is an address model where the private keys are managed by the Coinbase SDK. This means that the SDK can sign transactions on behalf of the user. In production, it’s recommend to use a server-signer for increased security.The example below illustrates how to stake from a wallet address.
import { Coinbase, Wallet } from "@coinbase/coinbase-sdk";// Create a new wallet address on the ethereum-holesky testnet network.let wallet = await Wallet.create({ networkId: Coinbase.networks.EthereumHolesky });// A newly created wallet will have no balance. Use wallet.getDefaultAddress()// to retrieve the default address and fund it with the required ETH.// Find out how much ETH is available to stake.let stakeableBalance = await wallet.stakeableBalance(Coinbase.assets.Eth);// Build a stake operation for an amount <= stakeableBalance, and in multiples of 32. In this case, 32 ETH.let stakingOperation = await wallet.createStake(32, Coinbase.assets.Eth, StakeOptionsMode.NATIVE);
Unstaking on native ETH requires a voluntary exit message to be signed by the validator and submitted to the network to initiate the unstaking process. For wallet addresses, this can be done two ways:
Coinbase managed unstake(recommended) : You can request Coinbase to begin unstaking your ETH. Coinbase will build a signed voluntary exit message and broadcast it to the network on your behalf.
import { Coinbase, Wallet } from "@coinbase/coinbase-sdk";// Create a new wallet address on the ethereum-holesky testnet network.let wallet = await Wallet.create({ networkId: Coinbase.networks.EthereumHolesky });// To know how much ETH balance across all your validators is available for staking// you can use the `unstakeableBalance` method as shown below.// Note: For Dedicated ETH Staking, the unstakeable balance depends on the validators owned by the CDP account,// not your wallet. We surface the unstakeable balance on the wallet object for simplicity.let unstakeableBalance = await wallet.unstakeableBalance(Coinbase.assets.Eth, StakeOptionsMode.NATIVE);// Build an unstake operation for an amount <= unstakeableBalance, and in multiples of 32. In this case, 32 ETH.let stakingOperation = await wallet.createUnstake(32, Coinbase.assets.Eth, StakeOptionsMode.NATIVE, {"immediate": "true"});
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.
import { Coinbase, Wallet } from "@coinbase/coinbase-sdk";// Create a new wallet address on the ethereum-holesky testnet network.let wallet = await Wallet.create({ networkId: Coinbase.networks.EthereumHolesky });// To know how much ETH balance across all your validators is available for staking// you can use the `unstakeableBalance` method as shown below.// Note: For Dedicated ETH Staking, the unstakeable balance depends on the validators owned by the CDP account,// not your wallet. We surface the unstakeable balance on the wallet object for simplicity.let unstakeableBalance = await wallet.unstakeableBalance(Coinbase.assets.Eth, StakeOptionsMode.NATIVE);// Build an unstake operation for an amount <= unstakeableBalance, and in multiples of 32. In this case, 32 ETH.let stakingOperation = await wallet.createUnstake(32, Coinbase.assets.Eth, StakeOptionsMode.NATIVE);
After unstaking, voluntary exit messages can be read and stored on your end and broadcasted to the network whenever you want to initiate the unstaking process. Refer to the Broadcasting Exit Messages section for an example.
The functionality below applies to both External and wallet addresses.
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.
Typescript
Go
Copy
Ask AI
// For Holesky, publicly available RPC URL's can be found here https://chainlist.org/chain/17000stakingOperation.getSignedVoluntaryExitMessages().forEach(async signedVoluntaryExitMessage => { let resp = await axios.post("HOLESKY_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 transaction are traditional EIP-1159 transactions that can be signed and broadcasted to the execution layer via a normal transaction flow.
Typescript
Go
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 Holesky, publicly available RPC URL's can be found here https://chainlist.org/chain/17000const provider = new ethers.JsonRpcProvider("HOLESKY_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);});
After staking an asset, you can view your staking rewards. This allows you to track the rewards earned over time from your staked assets.
Typescript
Go
SDK DocumentationRefer 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()));
View the USD value of rewards including conversion price and time.
Copy
Ask AI
// Loop through the rewards and print each staking reward's USD conversion informationrewards.forEach(reward => { console.log( `USD value: ${reward.usdValue()}, Conversion price: ${reward.conversionPrice().toString()}, Conversion time: ${reward.conversionTime().toISOString()}`, );});
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.
Typescript
Go
SDK DocumentationRefer 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.
Typescript
Go
Copy
Ask AI
// Get the validators that you've provisioned for staking.const validators = await Validator.list(Coinbase.networks.EthereumHolesky, Coinbase.assets.Eth);// Loop through the validators and print each validatorvalidators.forEach(validator => { console.log(validator.toString());});
The Validator object documentation is available here
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.
Typescript
Go
Copy
Ask AI
// Show all your validators with an active status.const validators = await Validator.list( Coinbase.networks.EthereumHolesky, Coinbase.assets.Eth, ValidatorStatus.ACTIVE,);
Example output
Your validators will be listed only if the status is active.
Copy
Ask AI
Id: 0xa3fc791b5abb4b83fe0e9fe2f6bc5a2728f967c5e845dd353cfac6d9ed4677ad39aa32ee25a1dbdaad8248d71ee1e3a4, Status: activeId: 0xadc25472f45a72446d0b5f7b5ec5760db14b198a21a8b0ad40ec673365c54ba1688ad0913f171135a94d4ce1f0ee684f, Status: activeId: 0x8071b39b9cfaefc094aff22c76a30f41709ed18f00b36efd63c7c64c644b3482bdfad5018fa32246af1a6c96943c750c, Status: active