Plutus Explained: Minting Policies

Welcome to the Plutus Explained series of blog posts where I seek to explain the Plutus programming language, in an accessible manner. In this post we will cover a minting policies. You may find it helpful to read about native tokens beforehand.

This post accompanies Week 5 of the Plutus Pioneers program.


A minting policy script determines whether or not a native token can be minted or burnt. It’s a big part of the minting scripts, but for the full version click on any of the files the Plutus Pioneer github repo

Example 1: A very simple policy script

{-# INLINABLE mkPolicy #-}
mkPolicy:: () -> ScriptContext -> Bool
mkPolicy () _ = True

-- Template Haskell
policy :: Scripts.MintingPolicy
policy = mkMintingPolicyScript $$(PlutusTx.compile [|| Scripts.wrapMintingPolicy mkPolicy ||])

This script just returns true in mkPolicy. The template Haskell section calls the mkPolicy.

Example 2: Allow minting if the transaction was signed by the same public key as the policy script

Recap: A UTXO is specified by an address (the public key hash). An output’s address / public key hash determines which transactions are allowed to ‘unlock’ the output and use it as an input. A transaction must be signed by the owner of the private key corresponding to the address / public key hash. Think of an address as a ‘lock’ that can only be ‘unlocked’ by the right ‘key’ ‒ the correct signature.

Ok with that out the way in this policy script we are checking to see if the transaction was signed by the same person as the public key hash of the policy script itself.

{-# INLINABLE mkPolicy #-}
mkPolicy :: PubKeyHash -> () -> ScriptContext -> Bool
mkPolicy pkh () ctx = txSignedBy (scriptContextTxInfo ctx) pkh

-- Template Haskell
policy :: PubKeyHash -> Scripts.MintingPolicy
policy pkh = mkMintingPolicyScript $
    $$(PlutusTx.compile [|| Scripts.wrapMintingPolicy . mkPolicy ||])
    `PlutusTx.applyCode`
    (PlutusTx.liftCode pkh)

pkh is the public key hash belonging to the policy script. txSignedBy returns true or false. We can take a quick look at the source code for txSigned by

{-# INLINABLE txSignedBy #-}
-- | Check if a transaction was signed by the given public key.
txSignedBy :: TxInfo -> PubKeyHash -> Bool
txSignedBy TxInfo{txInfoSignatories} k = case find ((==) k) txInfoSignatories of
    Just _  -> True
    Nothing -> False

We can see it is looking through all of the signatures of the transaction and checking if it matches the public key hash.

Notice also the difference in the template Haskell between the first and second example:

-- Example 1
policy :: Scripts.MintingPolicy
policy = mkMintingPolicyScript $$(PlutusTx.compile [|| Scripts.wrapMintingPolicy mkPolicy ||])

-- Example 2
policy :: PubKeyHash -> Scripts.MintingPolicy
policy pkh = mkMintingPolicyScript $
    $$(PlutusTx.compile [|| Scripts.wrapMintingPolicy . mkPolicy ||])
    `PlutusTx.applyCode`
    (PlutusTx.liftCode pkh)

In the first example, the policy function was fixed to mkPolicy which was always true. In the second example the outcome is determined by a parameter, the PubKeyHash. But this time round it becomes necessary to use apply / lift to compile the code into Plutus Core in order to determine whether its true or false.

Example 3: Allowing minting if it has not already been minted before.

This is a technique for minting NFTs where you only ever want 1 instance of a token.

{-# INLINABLE mkPolicy #-}
mkPolicy :: TxOutRef -> TokenName -> () -> ScriptContext -> Bool
mkPolicy oref tn () ctx = traceIfFalse "UTxO not consumed"   hasUTxO           &&
                          traceIfFalse "wrong amount minted" checkMintedAmount
  where
    info :: TxInfo
    info = scriptContextTxInfo ctx

    hasUTxO :: Bool
    hasUTxO = any (\i -> txInInfoOutRef i == oref) $ txInfoInputs info

    checkMintedAmount :: Bool
    checkMintedAmount = case flattenValue (txInfoForge info) of
        [(cs, tn', amt)] -> cs  == ownCurrencySymbol ctx && tn' == tn && amt == 1
        _                -> False

policy :: TxOutRef -> TokenName -> Scripts.MintingPolicy
policy oref tn = mkMintingPolicyScript $
    $$(PlutusTx.compile [|| \oref' tn' -> Scripts.wrapMintingPolicy $ mkPolicy oref' tn' ||])
    `PlutusTx.applyCode`
    PlutusTx.liftCode oref
    `PlutusTx.applyCode`
    PlutusTx.liftCode tn

Ok now it’s getting a little more complicated. The policy script went from this:

mkPolicy :: PubKeyHash -> () -> ScriptContext -> Bool 
mkPolicy pkh () ctx = txSignedBy (scriptContextTxInfo ctx) pkh 

to this

mkPolicy :: TxOutRef -> TokenName -> () -> ScriptContext -> Bool
mkPolicy oref tn () ctx = 
  traceIfFalse "UTxO not consumed"   hasUTxO 
  && traceIfFalse "wrong amount minted" checkMintedAmount

At the end of the day it still needs to just return a true or false. But this time instead of checking the signature of the transaction against the policy script owners public key hash, we are instead checking whether the UTxO (the fees being paid to mint) has not been used before, and that its minting the correct token. (In reality we would still want to also make sure only the script owner can mint the token)

The UTxOs being consumed (i.e. the fees) can be referenced using TxOutRef.

We see that it is using the two helper functions, hasUTxO and checkMintedAmount. My explanation is below, though I also recommend watching Lars’ explanation.

hasUTxO = any (\i -> txInInfoOutRef i == oref) $ txInfoInputs info

This is using an anonymous function (or lambda function as they are called in Python). It translates into:

  1. take all of the inputs of a transaction (txInfoInputs) from the info variable that was provided
  2. feed it into the lambda function in place of i
  3. which will then iterate through and check the txInInfoOutRef property of the txInfoInputs to see whether it matches the output reference of the UTxO it is referencing
checkMintedAmount = case flattenValue (txInfoForge info) of
        [(cs, tn', amt)] -> cs  == ownCurrencySymbol ctx && tn' == tn && amt == 1
        _                -> False

This code is taking txInfoForge from the Info variable, and using the flattenValue function to squeeze out the currency symbol, token name and amount.

After that it checks that:

  • the currency being minted in the transaction is the same as defined by this policy script
  • the token name being minted in the transaction is the same as this policy script
  • the amount being minted is only 1

…More to come

There’s a little bit more to come on this (covering HW1 and 2). But I’m going to get started on the following weeks material so I shall have to return this to this later.

Published by ReddSpark

Follow me on Twitter: https://twitter.com/Redd_Spark or YouTube https://www.youtube.com/@ReddSpark

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: