Staking API Tutorial

An example on how to send a transaction using Blockdaemon's BNB Staking API.

On this page, you will find a TypeScript example showing how to send a BNB transaction using the BNB Staking API. It includes functions for staking, deactivating stakes, and restaking.

import RpcClient from '@binance-chain/javascript-sdk/lib/rpc';
import { types, Transaction, crypto } from '@binance-chain/javascript-sdk';


const apiAddress = 'https://svc.blockdaemon.com/boss';

const REQUEST_OPTIONS = {
  method: 'POST',
  headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'X-API-Key': process.env['BOSS_API_KEY'],
      'X-Client-ID': process.env['X_CLIENT_ID'],
  },
};

const PRIVATE_KEY = process.env['PRIVATE_KEY'] || '';
if (!PRIVATE_KEY) {
  throw new Error('No private key found in environment');
}

const rpcClient = new RpcClient('https://data-seed-pre-0-s1.bnbchain.org', 'testnet');

async function signAndBroadcastDelegation(
  client: RpcClient,
  signBytes: string,
  privateKey: string,
): Promise<any> {
  const signMsg = hexToObject(signBytes);

  const privKeyBuf = Buffer.from(privateKey, 'hex');
  const signature = crypto.generateSignature(signBytes, privKeyBuf);

  if (signMsg.msgs.length !== 1) {
    throw new Error(`Invalid number of messages ${signMsg.msgs.length}`);
  }

  const delegateMsg = signMsg.msgs[0].value;

  const msg = new types.BscDelegateMsg({
    delegator_addr: delegateMsg.delegator_addr,
    validator_addr: delegateMsg.validator_addr,
    delegation: {
      denom: delegateMsg.delegation.denom,
      amount: parseInt(delegateMsg.delegation.amount),
    },
    side_chain_id: delegateMsg.side_chain_id,
  });

  return signAndBroadcastTx(client, signMsg, msg, privKeyBuf, signature);
}

async function signAndBroadcastUndelegation(
  client: RpcClient,
  signBytes: string,
  privateKey: string,
): Promise<any> {
  const signMsg = hexToObject(signBytes);

  const privKeyBuf = Buffer.from(privateKey, 'hex');
  const signature = crypto.generateSignature(signBytes, privKeyBuf);

  if (signMsg.msgs.length !== 1) {
    throw new Error(`Invalid number of messages ${signMsg.msgs.length}`);
  }

  const delegateMsg = signMsg.msgs[0].value;

  const msg = new types.BscUndelegateMsg({
    delegator_addr: delegateMsg.delegator_addr,
    validator_addr: delegateMsg.validator_addr,
    amount: {
      denom: delegateMsg.amount.denom,
      amount: parseInt(delegateMsg.amount.amount),
    },
    side_chain_id: delegateMsg.side_chain_id,
  });

  return signAndBroadcastTx(client, signMsg, msg, privKeyBuf, signature);
}

async function signAndBroadcastRedelegation(
  client: RpcClient,
  signBytes: string,
  privateKey: string,
): Promise<any> {
  const signMsg = hexToObject(signBytes);

  const privKeyBuf = Buffer.from(privateKey, 'hex');
  const signature = crypto.generateSignature(signBytes, privKeyBuf);

  if (signMsg.msgs.length !== 1) {
    throw new Error(`Invalid number of messages ${signMsg.msgs.length}`);
  }

  const delegateMsg = signMsg.msgs[0].value;
  const msg = new types.BscReDelegateMsg({
    delegator_addr: delegateMsg.delegator_addr,
    validator_src_addr: delegateMsg.validator_src_addr,
    validator_dst_addr: delegateMsg.validator_dst_addr,
    amount: {
      denom: delegateMsg.amount.denom,
      amount: parseInt(delegateMsg.amount.amount),
    },
    side_chain_id: delegateMsg.side_chain_id,
  });

  return signAndBroadcastTx(client, signMsg, msg, privKeyBuf, signature);
}

async function signAndBroadcastTx(
  client: RpcClient,
  signMsg: any,
  msg: types.BaseMsg,
  privKeyBuf: Buffer,
  signature: Buffer,
): Promise<any> {
  const tx = new Transaction({
    chainId: signMsg.chain_id,
    msg: msg.getMsg(),
    accountNumber: parseInt(signMsg.account_number),
    sequence: parseInt(signMsg.sequence),
    memo: '',
    source: 0,
  });

  tx.addSignature(crypto.generatePubKey(privKeyBuf), signature);

  return client.broadcastDelegate(tx);
}

function hexToObject(hexString: string) {
  const buffer = Buffer.from(hexString, 'hex');
  const jsonString = buffer.toString('utf-8');
  return JSON.parse(jsonString);
}

async function createStake(privateKey: string, requestOptions: any) {

  const request = {
    amount: '100000000',
    delegator_address: crypto.getAddressFromPrivateKey(
      privateKey,
      'bnb',
    ),
  };

  requestOptions.body = JSON.stringify(request);

  const response = await fetch(`${apiAddress}/v1/binance/testnet/stake-intents`, requestOptions);
  if (!response.ok) {
      throw new Error('Response from BOSS API was not ok: ' + await response.text());
  }

  const responseJson = await response.json();
  if (!responseJson.binance) {
      throw new Error('Response from BOSS API did not include binance field: ' + JSON.stringify(responseJson));
  }

  const broadcastResult = await signAndBroadcastDelegation(
    rpcClient,
    responseJson.binance.unsigned_transaction,
    privateKey,
  );

  if (broadcastResult.code !== 0) {
    throw new Error('Broadcast failed: ' + JSON.stringify(broadcastResult));
  }

  console.log('Broadcast successful: ' + JSON.stringify(broadcastResult));
}

async function createDeactivateStake(privateKey: string, requestOptions: any) {

  const request = {
    amount: '100000000',
    delegator_address: crypto.getAddressFromPrivateKey(
      privateKey,
      'bnb',
    ),
  };

  requestOptions.body = JSON.stringify(request);

  const response = await fetch(`${apiAddress}/v1/binance/testnet/deactivation-intents`, requestOptions);
  if (!response.ok) {
      throw new Error('Response from BOSS API was not ok: ' + await response.text());
  }

  const responseJson = await response.json();
  if (!responseJson.binance) {
      throw new Error('Response from BOSS API did not include binance field: ' + JSON.stringify(responseJson));
  }

  const broadcastResult = await signAndBroadcastUndelegation(
    rpcClient,
    responseJson.binance.unsigned_transaction,
    privateKey,
  );

  if (broadcastResult.code !== 0) {
    throw new Error('Broadcast failed: ' + JSON.stringify(broadcastResult));
  }

  console.log('Broadcast successful: ' + JSON.stringify(broadcastResult));
}

async function createRestake(privateKey: string, requestOptions: any) {

    const request = {
      amount: '100000000',
      delegator_address: crypto.getAddressFromPrivateKey(
        privateKey,
        'bnb',
      ),
      old_validator_address: 'bva1pnww8kx30sz4xfcqvn8wjhrn796nf4dq77hcpa',
    };

    requestOptions.body = JSON.stringify(request);

    const response = await fetch(`${apiAddress}/v1/binance/testnet/restake-intents`, requestOptions);
    if (!response.ok) {
        throw new Error('Response from BOSS API was not ok: ' + await response.text());
    }

    const responseJson = await response.json();
    if (!responseJson.binance) {
        throw new Error('Response from BOSS API did not include binance field: ' + JSON.stringify(responseJson));
    }

    const broadcastResult = await signAndBroadcastRedelegation(
      rpcClient,
      responseJson.binance.unsigned_transaction,
      privateKey,
    );

    if (broadcastResult.code !== 0) {
      throw new Error('Broadcast failed: ' + JSON.stringify(broadcastResult));
    }

    console.log('Broadcast successful: ' + JSON.stringify(broadcastResult));
}

createStake(PRIVATE_KEY, REQUEST_OPTIONS)
  .then(() => process.exit(0))
  .catch(err => {
      console.error(err);
      process.exit(1);
  });

// createDeactivateStake(PRIVATE_KEY, REQUEST_OPTIONS)
//   .then(() => process.exit(0))
//   .catch(err => {
//       console.error(err);
//       process.exit(1);
//   });

// createRestake(PRIVATE_KEY, REQUEST_OPTIONS)
//   .then(() => process.exit(0))
//   .catch(err => {
//       console.error(err);
//       process.exit(1);
//   });

👋 Need Help?

Contact us through email or our support page for any issues, bugs, or assistance you may need.