Clarity Post-Conditions: Letting Callers Bound What a Contract Can Move
Post-conditions are caller-side assertions that bound asset movement in a Stacks transaction. Here is what they enforce, what they miss, and how to check them.
09 Aug 2025, 21:54 UTC

A swap call can look correct in review and still execute against a price you did not expect. The contract's internal checks might be sound, but you are still trusting them. Clarity's post-conditions move part of that trust to the caller.
Post-conditions are assertions the sender attaches to a transaction, declaring which asset movements are acceptable. The runtime enforces them: if actual movements violate a declared bound, the whole transaction aborts and its state changes roll back. That makes them useful when you are calling a contract you do not fully control or have not fully audited.
What a post-condition is, and what it is not
A post-condition is declared by the sender, not written into the contract. It is attached per asset class — STX, fungible tokens, non-fungible tokens — with comparison modes such as equal, less-than-or-equal, greater-than-or-equal, and a none mode. Tooling helpers surface these modes, but the exact names differ across the Clarity language, the Stacks transactions library, and wallet interfaces.
Two limits matter. First, a post-condition constrains only the caller's own transaction; it does not protect other users. Second, it bounds asset movement, not arbitrary state writes. It is not a general reentrancy fix.
A worked example: capping a swap
Suppose you call a swap contract and want two guarantees: no more than 100 STX leaves your account, and at least 250 TOKEN arrives. You attach both bounds to the same contract-call transaction. If the contract tries to move more STX or returns less TOKEN, the runtime aborts the transaction and rolls back the state changes.
// Illustrative only — not tested. Helper names vary by library and version.
// Bound 1: sender's STX outflow <= 100 STX
// Bound 2: sender's TOKEN inflow >= 250 TOKEN
// Attach both bounds to the contract-call transaction, then sign and broadcast.
Confirm the exact syntax and helper names against the versions you actually use before relying on this shape. Older tutorial snippets may target a different Clarity version and may not compile or behave identically.
Why this is practical in Clarity specifically
Clarity is designed to be decidable: no unbounded loops and restricted dynamic dispatch. That makes contract behavior more analyzable ahead of time, which is what lets a caller set meaningful bounds instead of guessing. Post-conditions complement in-contract checks such as assertions, traits, and explicit error codes rather than replacing them.
Trade-offs and limitations
- Caller-only scope. Users who omit post-conditions gain no protection from them.
- Asset movement only. A post-condition will not stop a contract from writing unexpected state.
- Tight bounds break on legitimate change. If a contract's fees or token behavior change, bounds that were once correct can abort valid transactions.
- Version drift. Clarity 2 and Clarity 3 (Nakamoto-era) differ in language and tooling details, so verify against your toolchain's release notes.
- Name collision. Microsoft Clarity (analytics) and VMware Clarity (design system) are unrelated products; confirm which one the team means before publishing.
How to verify before you rely on it
- Write a minimal contract plus a test transaction with a deliberately impossible post-condition.
- Run it on a local devnet or simnet and confirm the transaction aborts and no state changes persist.
- Check the official Clarity language reference for current post-condition types and asset identifiers, and the Stacks transactions library docs for helper names.
- Inspect a confirmed transaction in a Stacks block explorer to see post-condition fields recorded on-chain alongside the call.
- Cross-check release notes for the Clarity version your toolchain targets.
Post-conditions are a caller-side seatbelt. They do not make a contract safe for everyone; they make one transaction bounded. If you are integrating a swap or any call that moves assets, decide the bounds first, then verify on a devnet before mainnet.
Review note: this draft reflects general behavior described in planning material, not a tested example. Verify syntax and helper names against the exact Clarity and Stacks library versions you target before publishing code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.