Implementation
Learn how to add post-conditions to protect your Stacks transactions.
Post-conditions are a security feature in Stacks that protect users from unexpected transaction outcomes. This tutorial will walk you through implementing post-conditions in your applications to ensure transactions behave exactly as users expect.
What you'll learn
Construct post-conditions using the
Pchelper APIAdd post-conditions to different transaction types
Configure post-condition modes for transaction security
Implement post-conditions for STX, fungible tokens, and NFTs
Handle semi-fungible tokens (SFTs) with post-conditions
Use Originator mode to protect only the transaction sender's assets (SIP-040)
Use the MAY SEND token condition to optionally cover an NFT/SFT transfer (SIP-040)
Guard staking operations with staking post-conditions (SIP-045)
Constrain PoX actions with PoX post-conditions (SIP-045)
Constructing post-conditions
The Pc helper in Stacks.js provides a BDD-inspired API for constructing post-conditions. Start with Pc.principal() to specify which address will be verified, then chain methods to define the condition.
import { Pc } from '@stacks/transactions';
// Basic structure of a post-condition
const postCondition = Pc
.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6')
.willSendEq(1000)
.ustx();The Pc helper uses method chaining to build conditions. Your IDE will provide auto-completion for available methods at each step.
Available transfer methods
Post-conditions support different comparison operators and asset types. Choose the appropriate method based on your security requirements.
STX and fungible token methods
Comparison methods available:
.willSendEq(amount)- Exactly equal to amount.willSendGte(amount)- Greater than or equal to amount.willSendGt(amount)- Greater than amount.willSendLte(amount)- Less than or equal to amount.willSendLt(amount)- Less than amount
Asset type methods
NFT-specific methods
Use willMaybeSendAsset() when an NFT/SFT transfer is conditional inside the contract and you want the transaction to succeed whether or not it moves. Available from Stacks epoch 3.4 (SIP-040).
PoX-specific methods (SIP-045)
PoX post-conditions constrain whether a principal performs a gated PoX action. They carry only a principal and a condition code, no asset or amount.
Staking and PoX post-conditions are introduced by SIP-045 and available from Stacks epoch 4.0.
Setting the post-condition mode
The post-condition mode determines how the Stacks protocol handles asset transfers not explicitly covered by your post-conditions. This is an important security setting.
Mode options:
PostConditionMode.Deny (default): Transaction fails if any unspecified transfers occur
PostConditionMode.Originator: Transaction fails only if unspecified transfers originate from the transaction's origin account; transfers between other principals are allowed (SIP-040, epoch 3.4+, live since March 2026)
PostConditionMode.Allow: Transaction allows transfers beyond specified post-conditions
Originator mode is intended for DeFi-style contract calls where intermediate asset routing between contracts is unpredictable. It applies Deny-style protection to the origin account's assets while permitting movements between other principals.
Common implementation patterns
STX transfer post-conditions
Protect STX transfers by specifying exact amounts or ranges.
Fungible token post-conditions
Ensure fungible tokens are transferred as expected in contract calls.
NFT transfer post-conditions
Control NFT ownership changes with specific post-conditions.
Use willNotSendAsset() to protect valuable NFTs from being transferred unexpectedly.
Semi-fungible token (SFT) post-conditions
SFTs require special handling as they have both fungible and non-fungible properties.
Multi-bin withdrawals (Bitflow DLMM, concentrated-liquidity pools) typically produce one willMaybeSendAsset() post-condition per affected bin. The position SFT for each bin may be burned (transferred to the contract) or kept depending on the remaining liquidity after the call. Pairing each per-bin condition with willSendLte conditions on the underlying FTs gives the signer a precise upper bound on what can leave their account while allowing the contract to skip burns for bins that retain liquidity.
Originator-mode post-conditions for DeFi (SIP-040)
When calling a contract that routes assets through several intermediate contracts, listing every hop in Deny mode is brittle. Originator mode restricts only the sender's own outflows and allows asset movement between other principals.
Pc.origin() is a convenience that binds the post-condition to the transaction's origin account (the signer of the standard authorization structure, not tx-sender, and unaffected by as-contract?).
Staking post-conditions (SIP-045)
Staking post-conditions guard staking STX, or modifying staked STX, for a principal. Calls to the pox-5 stake, register-for-bond, and stake-update functions are evaluated against these post-conditions, and the transaction is rejected if the conditions are not met (SIP-045). They use the same comparators as STX post-conditions, and amounts are denoted in uSTX.
.ustxToLock() constrains STX being locked for stacking, while .ustx() constrains STX being transferred. A staking operation does not transfer STX out of the account, so a plain STX post-condition will not cover it.
PoX post-conditions (SIP-045)
PoX post-conditions guard PoX state changes that do not alter locking status. This covers the pox-5 unstake, unstake-sbtc, update-bond-registration, and announce-l1-early-exit functions (SIP-045). Under SIP-045's Bitcoin staking framework, a participant's L1 commitment can be held as native BTC on Bitcoin L1 or as sBTC on Stacks; unstake-sbtc handles the sBTC-form withdrawal, and both are gated by the same PoX post-condition type.
Use willNotPerformPox() when calling an unfamiliar contract to guarantee it cannot change your PoX state as a side effect. Use willPerformPox() when the whole point of the transaction is a PoX action and you want the transaction to abort if it silently does not happen.
Multiple post-conditions
Complex transactions often require multiple post-conditions to fully protect all asset transfers.
Last updated
Was this helpful?