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. Construct post-conditions.
  2. Use post-conditions.
  3. Set the post-condition mode.
  4. Use the legacy methods/enums.

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.

import { Pc } from '@stacks/transactions';

const postCondition = Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6')
  .willSendEq(1000)
  .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.

import { PostConditionMode } from '@stacks/transactions';

const tx = await makeContractCall({
  // ...
  postConditionMode: PostConditionMode.Allow,
  // OR
  postConditionMode: PostConditionMode.Deny,
  // ...
});

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

Legacy methods/enums

Legacy methods and enums can be used interchangeably with the builder methods.

Note

If you prefer the legacy methods over the Pc builder approach, you can import them from the @stacks/transactions package.

import { createSTXPostCondition, FungibleConditionCode } from '@stacks/transactions';

const postCondition = createSTXPostCondition(
  "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6",
  FungibleConditionCode.Equal,
  1000
);

Some other examples can be found below:

Note

Use the Pc helpers to construct post-conditions in a clear and concise manner.


Amount uSTX sent

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

import { Pc } from '@stacks/transactions';

const postCondition = Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6')
  .willSendEq(1000)
  .ustx();

Amount FT sent

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

import { Pc } from '@stacks/transactions';

const postCondition = Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.token-ft')
  .willSendGte(500)
  .ft('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.token-ft', 'token');

Amount NFT sent / not sent

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

import { Pc } from '@stacks/transactions';

const postCondition = Pc.principal('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6')
  .willNotSendAsset()
  .nft('STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.token-nft::token', Cl.uint(12));
Note

Post-conditions can only ensure the transfer of assets and cannot guarantee the end-state after a transaction. To learn more, read the post-conditions guide.


Next steps