stacking

Stacking is implemented as a smart contract using Clarity. You can always find the Stacking contract identifier using the Stacks Blockchain API v2/pox endpoint.

Currently, stacking uses the pox-4 contract. The deployed pox-4 contract and included comments can be viewed in the explorer.

In this walkthrough, we'll cover the entire stacking contract from start to finish, including descriptions of the various functions and errors, and when you might use/encounter them.

Rather than walking through the contract line by line, which you can do by simply reading the contract code and the comments, we'll instead explore it from the perspective of conducting stacking operations, including solo stacking, delegating, and running a pool.

At the bottom you will find a list of some errors you may run into and their explanations.

There are a few utilities that make interacting with this contract easier including Leather Earn as an UI and the @stacks/stacking package for a JS library.

Hiro has a detailed guide available for stacking using this library as well as a Nakamoto guide specifically for the additions made to work with pox-4.

Prerequisites

If you are not familiar with stacking as a concept, it will be useful to familiarize yourself with that first before diving into the contract.


Solo Stacking

Solo stacking is the simplest option, and begins by calling the stack-stx function.

stack-stx

This function locks up the given amount of STX for the given lock period (number of reward cycles) for the tx-sender.

Here's the full code for that function, then we'll dive into how it works below that.

First let's cover the needed parameters.

  • amount-ustx is the amount of STX you would like to lock, denoted in micro-STX, or uSTX (1 STX = 1,000,000 uSTX).

  • pox-addr is a tuple that encodes the Bitcoin address to be used for the PoX rewards, details below.

  • start-burn-ht is the Bitcoin block height you would like to begin stacking. You will receive rewards in the reward cycle following start-burn-ht. Importantly, start-burn-ht may not be further into the future than the current reward cycle, and in most cases should be set to the current burn block height.

  • lock-period sets the number of reward cycles you would like you lock your STX for, this can be between 1 and 12.

  • signer-sig is a unique generated signature that proves ownership of this signer. Further details for its role and how to generate it can be found in the How to Stack document.

  • signer-key is the public key of your signer, more details in the How to Run a Signer document.

  • max-amount sets the maximum amount allowed to be stacked during the provided stacking period.

  • auth-id is a unique string to prevent re-use of this stacking transaction.

Supported Reward Address Types

For the pox-addr field, the version buffer must represent what kind of bitcoin address is being submitted. These are all the possible values you can pass here depending on your address type:

The hashbytes are the 20 hash bytes of the bitcoin address. You can obtain that from a bitcoin library, for instance using bitcoinjs-lib:

The stack-stx function performs several checks including:

  • The start-burn-ht results in the next reward cycle

  • The function is being called by the tx-sender or an allowed contract caller

  • The tx-sender is not currently stacking or delegating

  • The tx-sender has enough funds

  • The given signer-key is valid, proving ownership

  • Stacking can be performed (amount meets minimum threshold, lock period and bitcoin address are valid)

Next the function registers the provided PoX address for the next reward cycle, assigns its specific reward slot, and adds it to the stacking-state map, which keeps track of all current stackers per reward cycle.

Finally it returns the lock-up information so the node can carry out the lock. This step is what actually locks the STX and prevents the stacker from using them on-chain.

From here, the locked STX tokens will be unlocked automatically at the end of the lock period. The stacker can also call stack-increase or stack-extend to increase the amount locked or extend the time.


Delegated Stacking

Delegated stacking is essentially a multi-step process where delegators give pool operators permission to lock STX on their behalf. The typical flow:

1

Step: Delegator delegates their STX to a pool operator

The delegator calls delegate-stx to record that they delegate a given amount to a specific pool operator. This does not lock the STX — it only records the delegation permission.

2

Step: Pool operator stacks delegated STX (partial)

The pool operator calls delegate-stack-stx for each delegator they will lock on behalf of. This marks those STX as partially stacked (not yet in the official reward set).

3

Step: Pool operator commits aggregated locks

When the pool operator has aggregated enough delegated STX, they call stack-aggregation-commit-indexed (wraps inner-stack-aggregation-commit) to commit the aggregated stake into the reward set for the reward cycle.

There are also alternative actions like revoking delegation (see contract functions).


delegate-stx

This function is called by the individual stacker delegating their STX to a pool operator. An individual stacker choosing to delegate does not need to run their own signer.

This function does not actually lock the STX, but just allows the pool operator to issue the lock.

Parameters:

  • amount-ustx: amount delegating (uSTX)

  • delegate-to: Stacks address of the pool operator

  • until-burn-ht: optional expiry burn height for the delegation

  • pox-addr: optional Bitcoin address where this delegator wants rewards sent (if supplied, pool operator must send rewards to this address)

Checks: caller allowed, pox-addr version valid if provided, delegator not already delegating. Updates delegation-state. No STX are locked yet — the pool operator must call delegate-stack-stx.


delegate-stack-stx

Called by the pool operator to partially stack a delegator's STX.

This function validates the delegation record, ensures the delegator has the funds and is not already stacking, runs lightweight stacking checks, registers the partial stacked amount, and updates stacking-state. The STX remain partially stacked until the operator commits.


stack-aggregation-commit-indexed / inner-stack-aggregation-commit

The stack-aggregation-commit-indexed function wraps the private inner-stack-aggregation-commit. The private function commits partially stacked amounts into the reward set so each pox-addr obtains a reward-slot index.

Key points:

  • Validates caller and signer signature.

  • Validates stacking conditions.

  • Adds the aggregated pox-addr to the reward cycle and returns its reward-set index.

  • Deletes the partial-stacked entry and logs it.


How Stacking Reward Distribution Works

All of the above stacking functions take a pox-addr field that corresponds to a Bitcoin address where BTC rewards will be sent. It's important to understand how these addresses are used and how reward distribution is handled.

How Bitcoin rewards are distributed is primarily up to the discretion of the pool operator. Since PoX reward distributions are handled using Bitcoin transactions, there is currently not an effective way to automate their distribution to individual delegated stackers.

Role of pox-addr by function:

  • stack-stx: Bitcoin address for the solo stacker to receive rewards.

  • delegate-stx: Optional. If omitted, the pool operator decides where to send this delegator's rewards. If provided, the pool operator must send rewards to that address. Note: if provided, the delegator must have enough STX to meet the minimum stacking amount (each unique pox-addr consumes a reward slot).

  • delegate-stack-stx and stack-aggregation-commit-indexed: pox-addr is where the pool operator will receive BTC rewards for that aggregated stake. Pool operators typically use wrapper contracts or off-chain accounting to distribute BTC to delegators.


Errors

You may encounter several errors when trying to perform stacking operations. Below are some of the more common errors with explanations and how to resolve them.

Error 35 - ERR_INVALID_SIGNATURE_PUBKEY

This is likely the most common error you will encounter, and you'll usually see it in a failed stack-stx or stack-aggregation-commit transaction.

This error occurs in consume-signer-key-authorization which is called any time a signer signature is provided.

Possible causes:

  • The public key you used to generate the signer signature is not the same as the one you are passing in to the signer-key field.

  • One of the fields you passed in to generate your signer signature does not match the field you passed in to either the stack-stx or stack-aggregation-commit function.

How to fix: verify that the signer signature was generated using the exact same signer public key and parameters (amount, pox-addr/reward-cycle, lock period, max-amount, auth-id, etc.) as what you are passing into the contract call.

Error 4 - ERR_STACKING_NO_SUCH_PRINCIPAL

This error means the contract lookup for a partially stacked entry failed. The stacking contract looks up partially stacked STX (after delegate-stack-stx) by the key (pox-addr, stx-address, reward-cycle). If any of those parameters are wrong when generating the signature or calling stack-aggregation-commit, the lookup will fail.

How to fix: check that the pox-addr, stacker principal (stx address), and reward-cycle values match exactly what was used in delegate-stack-stx / the signature generation. See the stacking guide for delegation flow details.

Error 24 - ERR_INVALID_START_BURN_HEIGHT

This means the start-burn-height parameter parsed was invalid (it corresponded to a past or future cycle rather than the current next reward cycle). You will mostly see this in stack-stx or delegate-stack-stx failed transactions.

How to fix: set start-burn-ht to the current burn block height corresponding to the next reward cycle (or compute it using node APIs / libraries that map burn height to reward cycles).

Was this helpful?