Coinbase Staking API empowers developers to deliver a fully-featured staking experience in their applications using one common interface across protocols.

This quickstart shows you how to stake Hoodi (an Ethereum testnet) ETH to our best-in-class staking infrastructure using the Coinbase Staking API.

What you will learn

  • Installing the CDP SDK
  • How to check your stakeable balance
  • How to create a transaction to stake Hoodi ETH
  • How to sign and broadcast a staking transaction

1. Prerequisites

The Coinbase Typescript SDK requires Node.js version 18+. To verify your Node version:

node -v

2. API Key Setup

To use the CDP SDK, you need a CDP secret API key. If you need to create one, follow this guide.

Once you have the key, place it on your filesystem. You will need to reference this file path later in your code.

3. Create a Workspace

In your preferred shell, create a new directory:

mkdir staking-demo
cd staking-demo

4. Install the CDP SDK

Install the CDP SDK using your preferred package manager:

npm install @coinbase/coinbase-sdk

Match your wallet address model with your use cases

Staking can be done using two different models types depending on your use case.

  • External Address (recommended) - The External Address represents the End User Custody model where the private keys are not managed by the CDP SDK. All of our networks will support this address model. Read more in the External Addresses documentation.

  • Wallet Address - The Wallet Address represents the Developer Custody model where the private keys are managed by the CDP SDK. Only a subset of our networks support this address model. Find out more in the Wallet Addresses section.


5a. Create a Staking Transaction using an External Address

To proceed with the stake example below, you need some Hoodi ETH in your wallet. If you don’t have any, you can request some from the Ethereum Hoodi Faucet.

Create a new file named stake.ts and paste the code block below:

stake.ts
import {
    Coinbase,
    ExternalAddress,
    StakeOptionsMode,
} from "@coinbase/coinbase-sdk";

// highlight-start
const apiKeyFilePath = "YOUR_API_KEY_FILE_PATH";
const walletAddress = "YOUR_WALLET_ADDRESS";
// highlight-end

/**
 * Stake 0.005 ETH on the ethereum-hoodi testnet network.
 */
async function stake() {
Coinbase.configureFromJson({ filePath: apiKeyFilePath });

// Create a new external address on the ethereum-hoodi testnet network.
const address = new ExternalAddress(
    Coinbase.networks.EthereumHoodi,
    walletAddress,
);

// Find out how much ETH is available to stake.
const stakeableBalance = await address.stakeableBalance(
    Coinbase.assets.Eth,
    StakeOptionsMode.PARTIAL,
);

console.log("Stakeable balance of address %s is %s ETH", walletAddress, stakeableBalance);

// Build a stake transaction for an amount <= stakeableBalance
process.stdout.write("Building a transaction to stake 0.005 ETH...");
const stakingOperation = await address.buildStakeOperation(
    0.005,
    Coinbase.assets.Eth,
    StakeOptionsMode.PARTIAL,
);

console.log("Staking Operation ID: %s", stakingOperation.getID())

console.log("Done.");
}

(async () => {
try {
    await stake();
} catch (error) {
    console.error("Error during stake operation", error);
}
})();

Note

  • Be sure to replace the placeholder values with your own for:
YOUR_API_KEY_FILE_PATH
YOUR_WALLET_ADDRESS

Then run the code to create a staking transaction:

npx ts-node stake.ts

The transaction that was generated is an unsigned transaction. This still needs to be signed and broadcasted to the network to stake successfully. See the next section for instructions on how to sign and broadcast the transaction.

5b. Create a Staking Transaction using a Wallet Address

Create a new file named stake.ts and paste the code block below:

stake.ts
import { Coinbase, Wallet, StakeOptionsMode } from "@coinbase/coinbase-sdk";

// highlight-start
const apiKeyFilePath = "YOUR_API_KEY_FILE_PATH";
// highlight-end

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

/**
 * Perform a stake operation.
 */
async function stake() {
// Import the CDP API key from the file.
const coinbase = Coinbase.configureFromJson({ filePath: apiKeyFilePath });

// Create a wallet on the `ethereum-hoodi` testnet network.
process.stdout.write("Creating wallet...");
const wallet = await Wallet.create({ networkId: Coinbase.networks.EthereumHoodi });
console.log(
    `Wallet created with ID: ${wallet.getId()} and default address: ${(await wallet.getDefaultAddress()).getId()}`,
);

// Save wallet seed locally for future use.
process.stdout.write("Saving wallet seed information to local directory...");
wallet.saveSeed("wallet-seed", true);
console.log("Done.");

// Fund your wallet from a faucet with some ethereum-hoodi testnet funds.
process.stdout.write("Funding wallet with testnet funds...");
const faucetTransaction = await wallet.faucet();
await faucetTransaction.wait();

console.log("Done.");
console.log(`Wallet funding transaction: ${faucetTransaction.getTransactionLink()}`);

// Find out how much ETH is available to stake from your wallet.
process.stdout.write("Getting stakeable balance of wallet...");
const stakeableBalance = await wallet.stakeableBalance(Coinbase.assets.Eth, StakeOptionsMode.PARTIAL);
console.log("Done.");
console.log(`Stakeable balance of wallet: ${stakeableBalance} ETH`);

// Stake a small amount of ETH.
process.stdout.write("Staking 0.00001 ETH from your wallet...");
const stakingOperation = await wallet.createStake(0.00001, Coinbase.assets.Eth, StakeOptionsMode.PARTIAL);
console.log("Done.");
console.log(
    `View your stake transaction: ${stakingOperation.getTransactions()[0].getTransactionLink()}`,
);
}

(async () => {
try {
    await stake();
} catch (error) {
    console.error("Error during stake operation", error);
}
})();

Note

  • Be sure to replace the placeholder values with your own for:
YOUR_API_KEY_FILE_PATH

Then run the code to create a staking transaction:

npx ts-node stake.ts

Note

  • To reuse the wallet created in the steps above, see Re-instantiating a Wallet with the CDP SDK.
  • Network congestion can sometimes delay a transaction, which can cause the funding or stake transaction to fail.

6. [Optional]: Sign and Broadcast your Staking Transaction (External Address only)

This optional step is only applicable to external address model only

The external wallet address model assumes developers will sign and broadcast transactions outside of our SDK. The example below shows how this can be done using Ethers.js web library as an example.

The previous step generated an unsigned transaction. To stake successfully, the transaction needs to be signed and broadcasted to the network.

Signing and broadcasting functionality is added to the example from above. The additional lines are highlighted for clarity.

showLineNumbers stake.ts
import { Coinbase, ExternalAddress, StakeOptionsMode } from "@coinbase/coinbase-sdk";
// highlight-start
import { ethers } from "ethers";
// highlight-end

const apiKeyFilePath = "YOUR_API_KEY_FILE_PATH";
const walletAddress = "YOUR_WALLET_ADDRESS";

/**
 * Stake 0.005 ETH on the ethereum-hoodi testnet network.
 */
async function stake() {
Coinbase.configureFromJson({ filePath: apiKeyFilePath });

// Create a new external address on the ethereum-hoodi testnet network.
const address = new ExternalAddress(Coinbase.networks.EthereumHoodi, walletAddress);

// Find out how much ETH is available to stake.
const stakeableBalance = await address.stakeableBalance(Coinbase.assets.Eth, StakeOptionsMode.PARTIAL);
console.log("Stakeable balance of address %s is %s ETH", walletAddress, stakeableBalance);

// Build a stake transaction for an amount <= stakeableBalance
process.stdout.write("Building a transaction to stake 0.005 ETH... ");
const stakingOperation = await address.buildStakeOperation(0.005, Coinbase.assets.Eth, StakeOptionsMode.PARTIAL);
console.log("Done.");

// highlight-start
// Load your wallet's private key from which you initiated the above stake operation.
const walletPrivateKey = "YOUR_WALLET_PRIVATE_KEY";
const wallet = new ethers.Wallet(walletPrivateKey);
// Additional public Hoodi RPC endpoints can be found here https://chainlist.org/chain/560048
const hoodiNodeURL = "HOODI_NODE_URL";

// Sign the transactions within staking operation resource with your wallet.
process.stdout.write("Signing the stake operation... ");
await stakingOperation.sign(wallet);
console.log("Done.");

const provider = new ethers.JsonRpcProvider(hoodiNodeURL);

// Broadcast each of the signed transactions to the network.
process.stdout.write("Broadcasting the stake operation... ");
for (const tx of stakingOperation.getTransactions()) {
    const resp = await provider.broadcastTransaction(tx.getSignedPayload()!);
    console.log("Broadcasted transaction hash: %s", resp.hash);
}
// highlight-end
}

(async () => {
try {
    await stake();
} catch (error) {
    console.error("Error during stake operation", error);
}
})();

Note

  • Be sure to replace the placeholder values with your own for:
YOUR_API_KEY_FILE_PATH
YOUR_WALLET_ADDRESS

Run the code:

npx ts-node stake.ts

Visit the Etherscan block explorer to view the finalized transaction after it has been broadcasted. Testnet transactions may take up to a minute to be confirmed by a block explorer.

https://holesky.etherscan.io/tx/{BROADCASTED_TX_HASH}

Next Steps

Congratulations! You’ve used the CDP SDK to stake your first ETH on the Holesky testnet.

See also: