Deep dive into post-conditions

This guide explains how to use post-conditions to secure your smart contracts.


Post-conditions in Stacks transactions provide an additional layer of security. They ensure that transactions execute as expected without requiring the user to know the underlying smart contract code.

In this guide, you will learn how to:

  1. 1Construct post-conditions.
  2. 2Use post-conditions.
  3. 3Set the post-condition mode.
  4. 4Usage examples.

Constructing post-conditions

In Stacks.js, post-conditions can be constructed using the Pc helpers. These are inspired by Behavior Driven Development (BDD).

Start with the Pc.principal initializer to specify the address of the principal that will be verified in the post-condition. Then auto-complete the rest of the post-condition.

Using post-conditions

Post-conditions can be added to contract calls and FT/NFT transfers to ensure assets are transferred as specified.

For instance, the following post-condition ensures that the principal initiating the transaction must send exactly 1000 uSTX, or else the transaction will abort.

1
import { Pc } from '@stacks/transactions';
2
3
const postCondition = Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6')
4
.willSendEq(1000)
5
.ustx();

Setting the post-condition mode

When creating a transaction, the mode of the transaction can be set to Allow or Deny to specify whether unspecified asset transfers are permitted.

1
import { PostConditionMode } from '@stacks/transactions';
2
3
const tx = await makeContractCall({
4
// ...
5
postConditionMode: PostConditionMode.Allow,
6
// OR
7
postConditionMode: PostConditionMode.Deny,
8
// ...
9
});

Essentially, the postConditionMode is what tells the Stacks node whether to require (Deny) or ignore (Allow) the post-conditions when evaluating the transaction.

Usage examples

Amount uSTX sent

Construct a post-condition for a certain amount of uSTX to be sent.

1
import { Pc } from '@stacks/transactions';
2
3
const postCondition = Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6')
4
.willSendEq(1000)
5
.ustx();

Amount FT sent

Construct a post-condition for a certain amount of a specific FT to be sent.

1
import { Pc } from '@stacks/transactions';
2
3
const postCondition = Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.token-ft')
4
.willSendGte(500)
5
.ft('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.token-ft', 'token');

Amount NFT sent / not sent

Construct a post-condition for sending / not-sending a specific NFT.

1
import { Pc } from '@stacks/transactions';
2
3
const postCondition = Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6')
4
.willNotSendAsset()
5
.nft('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.token-nft::token', Cl.uint(12));

Amount SFT sent / not sent

Construct a post-condition for sending / not-sending a specific SFT (Semi-fungible token).

1
import { Cl, Pc } from '@stacks/transactions';
2
3
const postConditionForNFT = Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6')
4
.willSendAsset()
5
.nft(
6
"ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.semi-fungible-token::semi-fungible-token-id",
7
Cl.tuple({ "token-id": Cl.uint(1), owner: Cl.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6') })
8
);
9
10
const postConditionForFT = Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6')
11
.willSendEq(500)
12
.ft("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.semi-fungible-token", "semi-fungible-token");
Note

The previous builders (makeStandardSTXPostCondition, makeStandardFungiblePostCondition, etc) were removed in v7.0.0.


Next steps