Skip to main content

Integrating Stacking

Click to quick start a Stacks Connect project

In this tutorial, you'll learn how to integrate Stacking by interacting with the respective smart contract, as well as reading data from the Stacks blockchain.

This tutorial highlights the following capabilities:

  • Generate Stacks accounts
  • Display stacking info
  • Verify stacking eligibility
  • Add stacking action
  • Display stacking status

Prerequisites

First, you'll need to understand the Stacking mechanism.

You'll also need NodeJS 16 or higher to complete this tutorial. You can verify your installation by opening up your terminal and run the following command:

node --version

Overview

In this tutorial, we'll implement the Stacking flow laid out in the Stacking guide.

Step 1: Install dependencies

Install the Stacks.js stacking, network, and transactions libraries:

npm install --save @stacks/network @stacks/transactions @stacks/stacking
info

Step 2: Generating an account and initialization

To get started, let's create a new, random Stacks account:

import {
makeRandomPrivKey,
privateKeyToString,
getAddressFromPrivateKey,
TransactionVersion,
} from '@stacks/transactions';
import { StacksTestnet, StacksMainnet } from '@stacks/network';
import { StackingClient } from '@stacks/stacking';

// generate random key or use an existing key
const privateKey = privateKeyToString(makeRandomPrivKey());

// get Stacks address
// for mainnet, remove the TransactionVersion
const stxAddress = getAddressFromPrivateKey(privateKey, TransactionVersion.Testnet);

// instantiate the Stacker class for testnet
// for mainnet, use `new StacksMainnet()`
const client = new StackingClient(stxAddress, new StacksTestnet());
info

Review the accounts guide for more details

Step 3: Display stacking info

In order to inform users about the upcoming reward cycle, we can use the following methods to obtain information for Stacking:

With the obtained PoX info, you can present whether Stacking has been executed in the next cycle, when the next cycle begins, the duration of a cycle, and the minimum microstacks required to participate:

// will Stacking be executed in the next cycle?
const stackingEnabledNextCycle = await client.isStackingEnabledNextCycle();
// true or false

// how long (in seconds) is a Stacking cycle?
const cycleDuration = await client.getCycleDuration();
// 126000

// how much time is left (in seconds) until the next cycle begins?
const secondsUntilNextCycle = await client.getSecondsUntilNextCycle();
// 87120
note

Cycle duration and participation thresholds will differ between mainnet and testnet

You can also retrieve the raw PoX and core information using the methods below if required:

const poxInfo = await client.getPoxInfo();
// {
// contract_id: 'ST000000000000000000002AMW42H.pox-4',
// pox_activation_threshold_ustx: 829617368228220,
// first_burnchain_block_height: 2000000,
// current_burnchain_block_height: 2585174,
// prepare_phase_block_length: 50,
// reward_phase_block_length: 1000,
// reward_slots: 2000,
// rejection_fraction: null,
// total_liquid_supply_ustx: 41480868411411030,
// current_cycle: {
// id: 557,
// min_threshold_ustx: 5190000000000,
// stacked_ustx: 0,
// is_pox_active: false
// },
// next_cycle: {
// id: 558,
// min_threshold_ustx: 5190000000000,
// min_increment_ustx: 5185108551426,
// stacked_ustx: 17209000000000,
// prepare_phase_start_block_height: 2585850,
// blocks_until_prepare_phase: 676,
// reward_phase_start_block_height: 2585900,
// blocks_until_reward_phase: 726,
// ustx_until_pox_rejection: null
// },
// epochs: [],
// min_amount_ustx: 5190000000000,
// prepare_cycle_length: 50,
// reward_cycle_id: 557,
// reward_cycle_length: 1050,
// rejection_votes_left_required: null,
// next_reward_cycle_in: 726,
// contract_versions: [
// {
// contract_id: 'ST000000000000000000002AMW42H.pox',
// activation_burnchain_block_height: 2000000,
// first_reward_cycle_id: 0
// },
// {
// contract_id: 'ST000000000000000000002AMW42H.pox-2',
// activation_burnchain_block_height: 2422102,
// first_reward_cycle_id: 403
// },
// {
// contract_id: 'ST000000000000000000002AMW42H.pox-3',
// activation_burnchain_block_height: 2432545,
// first_reward_cycle_id: 412
// },
// {
// contract_id: 'ST000000000000000000002AMW42H.pox-4',
// activation_burnchain_block_height: 2583894,
// first_reward_cycle_id: 557
// }
// ]
// }

const coreInfo = await client.getCoreInfo();
// {
// peer_version: 4207599115,
// pox_consensus: '4459bdc64882b6ad642b77bb4c2ba03f289b2256',
// burn_block_height: 2585174,
// stable_pox_consensus: '1ef6719e2bc6661ec6ca7ad7df30612996bb8ef4',
// stable_burn_block_height: 2585167,
// server_version:
// 'stacks-node 2.5.0.0.0-rc1 (next:aaab6bc, release build, linux [x86_64])',
// network_id: 2147483648,
// parent_network_id: 118034699,
// stacks_tip_height: 153355,
// stacks_tip: 'b139daec6b10ea6e18016b8b999c1a5c471b9d7203502f6e37cf75b601c6b90b',
// stacks_tip_consensus_hash: '4459bdc64882b6ad642b77bb4c2ba03f289b2256',
// genesis_chainstate_hash:
// '74237aa39aa50a83de11a4f53e9d3bb7d43461d1de9873f402e5453ae60bc59b',
// unanchored_tip: null,
// unanchored_seq: null,
// exit_at_block_height: null,
// node_public_key:
// '025c6f7041f9e5263d9cd79590aa9b81a9ddbe0504e3d3b764fc2f27027586fa8a',
// node_public_key_hash: '93bd89dd38d0fbc318f9d2f49e897ccea17dff2c',
// affirmations: {
// heaviest: '',
// stacks_tip: '',
// sortition_tip: '',
// tentative_best: ''
// },
// last_pox_anchor: {
// anchor_block_hash:
// '9df9b081b9812870b1ec144d13e02829e1ca0725d56dbbf38122b5d1702f4155',
// anchor_block_txid:
// '25359b3669401cd5bfe5ef9390bd271eca0a6b457d205114f256ff4092e21223'
// },
// stackerdbs: []
// }

const targetBlocktime = await client.getTargetBlockTime();
// 120

Users need to have sufficient Stacks (STX) tokens to participate in Stacking. This can be verified easily:

const hasMinStxAmount = await client.hasMinimumStx();
// true or false

For testing purposes, you can use the faucet to obtain testnet STX tokens. Replace YOURSTXADDRESS below with your Stacks address:

curl 'https://api.testnet.hiro.so/extended/v1/faucets/stx?address=YOURSTXADDRESS&stacking=true' \
-X 'POST' \
-H 'origin: https://explorer.hiro.so' \
-H 'referer: https://explorer.hiro.so/' -i

You'll typically have to wait a few minutes for the transaction to complete, but can look up the progress via the returned txid and the Stacks explorer.

Users can select how many cycles they would like to participate in. To help with that decision, the unlocking time can be estimated:

// this would be provided by the user
let numberOfCycles = 3;

// the projected datetime for the unlocking of tokens
const unlockingAt = new Date(new Date().getTime() + secondsUntilNextCycle);
unlockingAt.setSeconds(unlockingAt.getSeconds() + cycleDuration * numberOfCycles);

Step 4: Verify stacking eligibility

At this point, your app shows Stacking details. If Stacking will be executed and the user has enough funds, the user should be asked to provide input for the amount of microstacks to lockup and a Bitcoin address to receive the pay out rewards.

With this input, and the data from previous steps, we can determine the eligibility for the next reward cycle:

let btcAddress = '1Xik14zRm29UsyS6DjhYg4iZeZqsDa8D3';
let numberOfCycles = 3;

const stackingEligibility = await client.canStack({
poxAddress: btcAddress,
cycles: numberOfCycles,
});
// {
// eligible: false,
// reason: 'ERR_STACKING_INVALID_LOCK_PERIOD',
// }
note

The eligibility check assumes the user will be stacking the maximum balance available in the account. The eligibility check is a read-only function call to the PoX smart contract which does not require broadcasting a transaction

If the user is eligible, the stacking action should be enabled on the UI. If not, the respective error message should be shown to the user.

Step 5: Lock STX to stack

Next, the Stacking action should be executed. For stacking, we need a signer signature (either from a signer you know, or generating one yourself if you host a signer).

A signer needs a signerPrivateKey for generating a signature. And a signerPublicKey for the Stacking contract to verify the signature.

// set the amount to lock in microstacks
const amountMicroStx = 100000000000n;

const poxInfo = await client.getPoxInfo(); // fetch pox-info for the current burn height

// generate a signature
const signerPrivateKey = YOUR_SIGNER_PRIVATE_KEY;
const signerPublicKey = YOUR_SIGNER_PUBLIC_KEY;
const signature = client.signPoxSignature({
topic: 'stack-stx',
period: numberOfCycles,
rewardCycle: poxInfo.reward_cycle_id,
poxAddress: btcAddress,
signerPrivateKey: signerPrivateKey,
maxAmount: amountMicroStx, // or more
authId: 123 // random number used to avoid conflicts
});
// execute the stacking action by signing and broadcasting a transaction to the network
const { txid } = await client.stack({
amountMicroStx,
poxAddress: btcAddress,
cycles: numberOfCycles,
burnBlockHeight: poxInfo.current_burnchain_block_height,
signerKey: signerPublicKey,
signerSignature: signature,
maxAmount: amountMicroStx,
authId: 123,
privateKey,
});
// f6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481

Step 6: Display Stacking status

With the completed transactions, Stacks tokens are locked up for the lockup duration. During that time, your app can display the following details: unlocking time, amount of Stacks locked, and bitcoin address used for rewards.

// If stacking is active for the account, you will receive the stacking details
// otherwise an error will be thrown
const stackingStatus = await client.getStatus();
// {
// stacked: true,
// details: {
// amount_microstx: '80000000000000',
// first_reward_cycle: 18,
// lock_period: 10,
// burnchain_unlock_height: 3020,
// pox_address: {
// version: '00',
// hashbytes: '05cf52a44bf3e6829b4f8c221cc675355bf83b7d'
// }
// }
// }
note

The pox_address property is the PoX contract's internal representation of the reward BTC address.

To display the unlocking time, you need to use the firstRewardCycle and the lockPeriod fields.

Congratulations! With the completion of this step, you successfully learnt how to ...

  • Generate Stacks accounts
  • Display stacking info
  • Verify stacking eligibility
  • Add stacking action
  • Display stacking status

Optional: Rewards

Currently, the Stacking library does not provide methods to get the paid rewards for a set address. However, the Stacks Blockchain API exposes endpoints to get more details.

As an example, if you want to get the rewards paid to btcAddress, you can make the following API call:

# for mainnet, replace `testnet` with `mainnet`
curl 'https://api.testnet.hiro.so/extended/v1/burnchain/rewards/<btcAddress>'