Polkadot API Usage Example

View some examples showing how to use Blockdaemon Polkadot Staking API.

Polkadot API Usage Example

In this section you will find TypeScript examples demonstrating how to send staking transactions using the Polkadot Staking API.

Broadcasting a transaction

The Blockdaemon Staking API returns unsigned transactions that must be signed and broadcast to the network by the user of the API in order to achieve the desired outcome. For Polkadot, this is usually done using the standardized Polkadot RPC endpoints and the Polkadot.js client.

Here is an example for signing and broadcasting a transaction to the Polkadot network:

import { ApiPromise, HttpProvider, WsProvider } from '@polkadot/api';
import type { KeyringPair } from '@polkadot/keyring/types';

// An example of signing and submitting a Polkadot transaction:
function signAndSubmitTx(
  api: ApiPromise,      // The Polkadot.js API client.
  account: KeyringPair, // The account you must sign this transaction with
                        // eg. your controller account.
  tx: string,           // The transaction itself.
): Promise<string> {
  const nonce = await api.rpc.system.accountNextIndex(account.address);

  return (await api.tx(tx).signAndSend(account, { nonce })).toHex();
}

// A convenient Polkadot connection error class.
export class PolkadotConnectionError extends Error {
  constructor(msg: string) {
    super('Can not connect to Polkadot: ' + msg);
  }
}

// A function that gets the URL of a Polkadot network node RPC
// and returns a Polkadot.js API object:
export async function connectToPolkadot(rpcUrl: string): Promise<ApiPromise> {
  try {
    const provider =
      rpcUrl.startsWith('ws://') || rpcUrl.startsWith('wss://')
        ? new WsProvider(rpcUrl)
        : rpcUrl.startsWith('http://') || rpcUrl.startsWith('https://')
        ? new HttpProvider(rpcUrl)
        : throwError(`Unsupported RPC URL: '${rpcUrl}'`);
    return await ApiPromise.create({ provider, throwOnConnect: true });
  } catch (err: any) {
    throw new PolkadotConnectionError(err.toString());
  }
}

const rpcUrl = "wss://example.com/Polkadot";

myAccount = (your account)

api = await connectToPolkadot(rpcUrl);

signAndSubmitTx(api, myAccount, myTx);

Creating a stake intent

Polkadot provides a solution for granting someone else the authority to manage your staking activities without granting direct access to your tokens. To accomplish this, the protocol differentiates between a "stash" account, which holds your tokens, and a "controller" account, which controls the staking process.

Certain operations involving these tokens, such as withdrawals and transfers, can only be performed by the stash account. On the other hand, actions like staking and unstaking can be carried out by the controller account. This separation allows you to keep the stash account keys securely in cold storage, while utilizing only the controller account. Even if the controller account is compromised, your tokens remain protected from unauthorized access.

By granting Blockdaemon access to your controller account, your tokens will remain secure in your stash account, accessible only to you. However, this means that you will lose the ability to personally stake or unstake your tokens if you choose to do so. To address this limitation, Blockdaemon takes an additional step by introducing a "proxy" account. This proxy account is nominated by your controller account to perform staking on your behalf, while still retaining your ability to stake independently.

If you are dissatisfied with how Blockdaemon manages your staking, you have the freedom to withdraw the nomination of the proxy account at any time and resume staking through your controller account.

Here is an example of how to create a stake intent with us. The provided transaction, once signed and sent, designates our proxy account to stake on behalf of your controller account.

// Set here your Blockdaemon customer ID:
const customerID = "customer-1";

// Set here your Blockdaemon BOSS API key:
const bossApiKey = "1234567890abcdef";

// Set here the Polkadot network you want to stake on:
const polkadotNetwork = "westend";
// Use "mainnet" for real tokens and "westend" for testing tokens.

// Set here the Polkadot address (public key) of your controller account:
const controllerAddress = "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y";

// Set here the HTTP address of the Blockdaemon public api:
const BossApiAddress = "https://svc.blockdaemon.com";

// A function to call Blockdaemon API to get back the data you need:
function createStakeIntent(
  bossApiKey: string,
  customerId: string,
  controllerAddress: string,
): Promise {
  const callData = {
    controller_address: controllerAddress,
  }

  const requestOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/json',
      'X-API-Key': bossApiKey,
      'X-Client-ID': customerId,
    },
    body: JSON.stringify(callData),
  };

  // return the response from POST Create a New Stake Intent
    return fetch(
      `${BossApiAddress}/v1/polkadot/${polkadotNetwork}/stake-intents`,
    requestOptions,
  ).then(async response => {
    if (response.status != 200) {
      throw await response.json();
    }
    return response.json() as Promise
  })
}

// The structure of the BOSS API call response:
function stakeIntentResponseExample() {
  const stakeIntentResponse = await createStakeIntent(
    bossAPIKey, customerId, controllerAddress);

  // Your customer ID (the same that you submitted):
  customerId = stakeIntentResponse.customer_id;

  // The unique ID for the stake intent you just created:
  stakeIntentId = stakeIntentResponse.stake_intent_id;

  // Should always be "polkadot":
  protocol = stakeIntentResponse.protocol;

  // The network you submitted ("mainnet" or "westend"):
  network = stakeIntentResponse.network;

  // The unique ID of your customer plan:
  planId = stakeIntentResponse.plan_id;

  // Your controller account address (the same that you submitted):
  controllerAddress = stakeIntentResponse.polkadot.controller_address;

  // The address of the proxy account you will nominate:
  proxyAddress = stakeIntentResponse.polkadot.proxy_address;

  // An unsigned transaction that will do the nomination
  // when / if you sign and submit it (see the first example):
  unsignedTransaction = stakeIntentResponse.polkadot.unsigned_transaction;
}

Deactivating a stake intent

When you deactivate a stake intent, you have the option to specify the amount of DOT tokens you want to free. If you wish to free all the tokens you have staked, it will automatically revoke the nomination of our proxy account to stake for your controller account. If you do not specify an amount, this option will be selected by default. However, if you choose to free only a portion of your staked tokens, we will assume that you still want us to continue staking the remaining amount, and the nomination will not be revoked.

In both cases, the tokens you requested will be marked for freeing from the staking bond. However, the actual freeing of these tokens will occur only at the end of the Polkadot era, which is the period for which they are staked.

Here is an example of how to deactivate your stake intent with us. The following transaction, once signed and sent, will mark the specified amount of tokens for freeing. If you choose to free all staked tokens, it will also revoke the nomination of our proxy account to stake for your controller account.

// Set here your Blockdaemon customer ID:
const customerID = "customer-1";

// Set here your Blockdaemon BOSS API key:
const bossApiKey = "1234567890abcdef";

// Set here the Polkadot network you want to stake on:
const polkadotNetwork = "westend";
// Use "mainnet" for real tokens and "westend" for testing tokens.

// Set here the Polkadot address (public key) of your controller account:
const controllerAddress = "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y";

// Set here the amount of DOT tokens you want to free from the staking:
const dotAmount = "1000";

// Set here the HTTP address of the Blockdaemon public api:
const BossApiAddress = "https://svc.blockdaemon.com";

function createDeactivateIntent(
  bossApiKey: string,
  customerId: string,
  controllerAddress: string,
  dotAmount: string,
): Promise {
  const callData = {
    controller_address: controllerAddress,
    amount: dotAmount,
  }

  const requestOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/json',
      'X-API-Key': bossApiKey,
      'X-Client-ID': customerId,
    },
    body: JSON.stringify(callData),
  };

  // return the response from POST Create a New Deactivate Intent
    return fetch(
      `${BossApiAddress}/v1/polkadot/${polkadotNetwork}/deactivation-intents`,
    requestOptions,
  ).then(async response => {
    if (response.status != 200) {
      throw await response.json();
    }
    return response.json() as Promise
  })
}

// The structure of the BOSS API call response:
function deactivateIntentResponseExample() {
  const deactivateIntentResponse = await createDeactivateIntent(
    bossAPIKey, customerId, controllerAddress, dotAmount);

  // Your customer ID (the same that you submitted):
  customerId = deactivateIntentResponse.customer_id;

  // The unique ID for the deactivate intent you just created:
  stakeIntentId = deactivateIntentResponse.stake_intent_id;

  // Should always be "polkadot":
  protocol = deactivateIntentResponse.protocol;

  // The network you submitted ("mainnet" or "westend"):
  network = deactivateIntentResponse.network;

  // Your controller account address (the same that you submitted):
  controllerAddress = deactivateIntentResponse.polkadot.controller_address;

  // The address of the proxy account you will nominate:
  proxyAddress = deactivateIntentResponse.polkadot.proxy_address;

  // An unsigned transaction that will do the deactivation
  // when / if you sign and submit it (see the first example):
  unsignedTransaction = deactivateIntentResponse.polkadot.unsigned_transaction;
}

Withdrawing staked tokens

Once you deactivate a stake intent, whether partially or completely, the locked amount of DOT will no longer be held in staking. Instead, it will remain in the "staking" section of your account. While they remain in this section, you have the option to stake them again, but you cannot freely transfer them to another account.

To transfer these tokens to another account, you need to "withdraw" them to the "free usage" section of your account. Once you complete this action, you are free to spend or utilize them as desired.

Here's an example of how to withdraw all deactivated tokens from your account. The following transaction, when signed and broadcast, will release all deactivated tokens from staking.

// Set here your Blockdaemon customer ID:
const customerID = "customer-1";

// Set here your Blockdaemon BOSS API key:
const bossApiKey = "1234567890abcdef";

// Set here the Polkadot network you want to stake on:
const polkadotNetwork = "westend";
// Use "mainnet" for real tokens and "westend" for testing tokens.

// Set here the Polkadot address (public key) of your controller account:
const controllerAddress = "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y";

// Set here the HTTP address of the Blockdaemon public api:
const BossApiAddress = "https://example.blockdaemon.com";

function createWithdrawIntent(
  bossApiKey: string,
  customerId: string,
  controllerAddress: string,
): Promise {
  const callData = {
    controller_address: controllerAddress,
  }

  const requestOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/json',
      'X-API-Key': bossApiKey,
      'X-Client-ID': customerId,
    },
    body: JSON.stringify(callData),
  };

  // return the response from POST Create a New Deactivate Intent
    return fetch(
      `${BossApiAddress}/v1/polkadot/${polkadotNetwork}/withdrawal-intents`,
    requestOptions,
  ).then(async response => {
    if (response.status != 200) {
      throw await response.json();
    }
    return response.json() as Promise
  })
}

// The structure of the BOSS API call response:
function withdrawIntentResponseExample() {
  const withdrawIntentResponse = await createWithdrawIntent(
    bossAPIKey, customerId, controllerAddress);

  // Your customer ID (the same that you submitted):
  customerId = withdrawIntentResponse.customer_id;

  // The unique ID for the withdraw intent you just created:
  stakeIntentId = withdrawIntentResponse.stake_intent_id;

  // Should always be "polkadot":
  protocol = withdrawIntentResponse.protocol;

  // The network you submitted ("mainnet" or "westend"):
  network = withdrawIntentResponse.network;

  // Your controller account address (the same that you submitted):
  controllerAddress = withdrawIntentResponse.polkadot.controller_address;

  // An unsigned transaction that will do the withdrawal
  // when / if you sign and submit it (see the first example):
  unsignedTransaction = withdrawIntentResponse.polkadot.unsigned_transaction;
}