For the complete documentation index, see llms.txt. This page is also available as Markdown.

buildStakeUpdate

Builds an unsigned contract call to pox-5 stake-update, which changes the sender's active STX-only stake. One call can extend the lock, add STX and switch signer-manager.


Usage

import {
  buildStakeUpdate,
  fetchEligibleStakeUpdate,
  fetchStakerInfo,
} from '@stacks/bitcoin-staking';
import {
  Pc,
  TransactionSigner,
  broadcastTransaction,
  fetchNonce,
  privateKeyToAddress,
  privateKeyToPublic,
  publicKeyToHex,
} from '@stacks/transactions';

const network = 'mainnet';
const privateKey = process.env.STAKER_KEY!;
const staker = privateKeyToAddress(privateKey, network);

const info = await fetchStakerInfo({ address: staker, network });
if (!info.staked) throw new Error('no active STX-only stake');
const current = info.details.signer;

const cyclesToExtend = 3;
const amountIncrease = 500_000n; // 0.5 STX in micro-STX

const check = await fetchEligibleStakeUpdate({
  staker,
  signerManager: current,
  oldSignerManager: current,
  cyclesToExtend,
  amountIncrease,
  network,
});
if (!check.ok) throw new Error(`stake-update would fail: ${check.reasons}`);

const tx = await buildStakeUpdate({
  signerManager: current, // same signer-manager: no switch
  oldSignerManager: current,
  cyclesToExtend,
  amountIncrease,
  publicKey: publicKeyToHex(privateKeyToPublic(privateKey)),
  fee: 10_000n, // micro-STX
  nonce: await fetchNonce({ address: staker, network }),
  network,
  postConditions: [Pc.principal(staker).willSendEq(amountIncrease).ustxToLock()],
});

new TransactionSigner(tx).signOrigin(privateKey);
const result = await broadcastTransaction({ transaction: tx, network });

Notes

  • Changes apply from the next reward cycle. The current cycle keeps the old amount and signer-manager (pox-5.clar L1088-L1173).

  • pox-5 recomputes the remaining lock from the next cycle, unlock-cycle - current-cycle - 1, and requires it to be 1 to 96 (ERR_INVALID_NUM_CYCLES (u20)). In the stake's final locked cycle that value is 0, so a call that does not extend fails with u20: extend in the same call.

  • Send it during the reward phase of any cycle, which excludes its last prepareCycleLength Bitcoin blocks (100 on mainnet), the prepare phase. In the prepare phase it fails with ERR_STAKE_IN_PREPARE_PHASE (u47).

  • Fails with ERR_NOT_STAKING (u27) without an active STX-only stake, and ERR_INVALID_OLD_SIGNER_MANAGER (u36) when oldSignerManager is not the recorded signer-manager.

  • signerManager must be registered (ERR_SIGNER_NOT_FOUND (u23)) with an active signer key grant (ERR_SIGNER_KEY_GRANT_NOT_FOUND (u17)), even when it is unchanged. Its validate-stake! can reject the update with its own error code.

  • The sender's unlocked balance must cover amountIncrease (ERR_INSUFFICIENT_STX (u8)). Locked STX does not count here, unlike in stake.

  • fetchEligibleStakeUpdate dry-runs every check except the signer-manager's validate-stake!.

  • Attach a staking post condition for amountIncrease with ustxToLock(), as in the Usage example. Without it, the default deny mode aborts a top-up with abort_by_post_condition.

  • A txid from broadcastTransaction confirms submission only. The call can still fail on-chain: read the mined transaction's result and pass an (err uN) to parsePox5Error.


Signature

The transaction fields come from TxParams.


Returns

Promise<StacksTransactionWire>

Resolves to an unsigned StacksTransactionWire that calls pox-5 stake-update. Sign it with TransactionSigner, then send it with broadcastTransaction.


Parameters

args.signerManager (required)

  • Type: string

Contract principal of the signer-manager to use from the next cycle. Pass the current one to keep it.

args.oldSignerManager (required)

  • Type: string

Contract principal of the signer-manager recorded for the stake now. fetchStakerInfo returns it as details.signer.

args.cyclesToExtend (optional)

  • Type: number

Reward cycles to add to the lock. Defaults to 0.

args.amountIncrease (optional)

  • Type: IntegerType

STX to add to the lock, in micro-STX. Defaults to 0n.

args.signerCalldata (optional)

  • Type: Uint8Array | string

Up to 500 bytes, as bytes or hex, that pox-5 forwards unread to the signer-manager's validate-stake!. Omitted, the contract receives none. buildSignerCalldata encodes an L1 BTC payout election for signer-managers that read that format.

args.fee (required)

  • Type: IntegerType

Transaction fee in micro-STX.

args.nonce (required)

  • Type: IntegerType

Nonce of the origin account. Read it with fetchNonce.

args.network (required)

  • Type: StacksNetworkName | StacksNetwork

A name such as 'mainnet' or 'testnet', or a StacksNetwork object. Sets the contract address (the network's boot address, SP000000000000000000002Q6VF78 on mainnet) and the transaction version and chain ID.

args.publicKey (required for a single-sig origin)

  • Type: string

Hex public key of the account that signs the transaction.

args.publicKeys and args.numSignatures (required for a multisig origin)

  • Type: PublicKey[] and number

Pass these instead of publicKey to build for an M-of-N multisig origin. This package defaults useNonSequentialMultiSig to true; pass false for the sequential hash mode. Pass address to check the key order against the multisig address.

args.postConditions (optional)

  • Type: PostCondition[]

Post conditions to attach. The builder adds none.

args.postConditionMode (optional)

  • Type: PostConditionModeName

'allow', 'deny' or 'originator'. Defaults to 'deny'.

Was this helpful?