Skip to main content

Deep Dive Post-Conditions

In Stacks, transactions can have "post-conditions". These are an additional security to ensure the transaction was executed as expected, without having the user needing to know the code of a smart contract.

Post-conditions can be used on contract calls and FT/NFT transfers. Adding post-conditions to a transaction can ensure that:

  • STX tokens were transferred from an address
  • FTs/NFTs we transferred from an address

Caveats

Post-conditions aren't perfect! They can't say anything about the end-state after a transaction. So, they can't guarantee the receival of FTs/NFTs, since they only check for sending.

Post-Condition Modes

All given post-conditions will always be checked. However, in addition to the post-conditions itself, we can also specify a "mode" for the transaction to verify other asset transfers. The mode can be either Allow or Deny.

  • Allow means that the transaction can transfer any asset (assuming no conflicting post-conditions).
  • Deny means the transaction will fail if any asset transfers (not specified in the post-conditions) are attempted.

Think of the mode as "allow/deny transfer of unspecified assets".

With Stacks.js we can set the mode via an exported enums from @stacks/transactions.

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

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

Constructing Post-Conditions

The easiest way to construct a post-condition in Stacks.js is to use the Pc helpers. This is a builder inspired by BDD (Behavior Driven Development).

Start with the Pc.address 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.

Methods

Builder initializer

  • Pc.principal(address: string) to initialize the principal that will be verified in the post-condition

STX/FT transfer methods

  • .willSendEq(amount: number) to specify the exact amount to be sent
  • .willSendGte(amount: number) to specify the greater than or equal amount to be sent
  • .willSendGt(amount: number) to specify the greater than amount to be sent
  • .willSendLte(amount: number) to specify the less than or equal amount to be sent
  • .willSendLt(amount: number) to specify the less than amount to be sent
  • .ustx() to specify uSTX as the FT asset (ends the builder)
  • .ft(contract: string, tokenName: string) to specify a specific FT asset (ends the builder)

NFT transfer methods

  • .willSendAsset() to specify an asset should be sent
  • .willNotSendAsset() to specify an asset should not be sent
  • .nft(asset: string, assetId: ClarityValue) to specify a specific NFT asset (ends the builder)

Legacy methods/enums

The builder methods construct the same representation as the legacy methods and can be used interchangeably.

Examples

Amount uSTX Sent

With Stacks.js, we can 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();

// Equivalent to:
// createSTXPostCondition(
// "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6",
// FungibleConditionCode.Equal,
// 1000
// );

Amount FT Sent

With Stacks.js, we can 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');

// Equivalent to:
// createFungiblePostCondition(
// "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.token-ft",
// FungibleConditionCode.GreaterEqual,
// 500,
// "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.token-ft::token"
// );

Amount NFT Sent

With Stacks.js, we can construct a post-condition for sending / not-sending a specific NFT instance.

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

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

// Equivalent to:
// createNonFungiblePostCondition(
// "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6",
// NonFungibleConditionCode.DoesNotSend,
// "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6.token-nft::token",
// Cl.uint(12)
// );