Cardano Plutus Developer Cheatsheet

Introduction: Understanding Plutus

Plutus is Cardano’s smart contract platform that enables developers to write applications that interact with the Cardano blockchain. Built on Haskell, Plutus offers a secure, functional programming approach to blockchain development.

Key Components:

  • Plutus Core: The on-chain language that executes on the Cardano blockchain
  • Plutus Tx: The compiler that translates Haskell code to Plutus Core
  • Plutus Application Framework (PAF): Libraries for building DApps
  • Extended UTXO Model (eUTXO): Cardano’s transaction model

Core Concepts

Extended UTXO Model

Unlike Ethereum’s account-based model, Cardano uses an extended UTXO model that combines Bitcoin’s UTXO approach with the ability to carry script state.

Key Differences:

  • Deterministic transaction validation
  • Increased parallelism
  • Local state validation
  • Explicit state management

Datum and Redeemer

ConceptDescriptionUse Case
DatumData attached to a UTXOScript state, token metadata
RedeemerInput provided when spending a UTXOTransaction parameters, action choice
ContextInformation about the transactionValidation logic, constraints
ValidatorScript that controls spending conditionsSmart contract logic

Script Address Types

  • Minting Policies: Control token creation (Native assets)
  • Spending Validators: Control UTXO spending
  • Staking Validators: Control stake delegation and rewards

Plutus Programming Basics

Common Imports

import Plutus.V1.Ledger.Api
import Plutus.V1.Ledger.Contexts
import Plutus.V2.Ledger.Api
import Plutus.V2.Ledger.Contexts
import PlutusTx.Prelude
import qualified PlutusTx
import Ledger hiding (singleton)
import Ledger.Typed.Scripts as Scripts
import Ledger.Value

PlutusTx.Prelude Functions

FunctionDescriptionExample
traceIfFalseLog message if condition is falsetraceIfFalse "Invalid amount" (amount > minAmount)
traceErrorLog error message and failtraceError "Transaction must include deadline"
emptyByteStringCreate empty ByteStringemptyByteString
appendByteStringAppend ByteStringsappendByteString bs1 bs2

Basic Validator Template

{-# INLINABLE mkValidator #-}
mkValidator :: Datum -> Redeemer -> ScriptContext -> Bool
mkValidator datum redeemer ctx = 
    traceIfFalse "Validation failed" condition
  where
    txInfo :: TxInfo
    txInfo = scriptContextTxInfo ctx
    
    condition :: Bool
    condition = True  -- Your validation logic here

validator :: Scripts.Validator
validator = mkValidatorScript $$(PlutusTx.compile [|| mkValidator ||])

valHash :: ValidatorHash
valHash = Scripts.validatorHash validator

scrAddress :: Address
scrAddress = scriptAddress validator

Datum and Redeemer Types

data MyDatum = MyDatum
    { beneficiary :: PubKeyHash
    , amount      :: Integer
    , deadline    :: POSIXTime
    }
    
data MyRedeemer = Spend | Cancel

PlutusTx.makeIsDataIndexed ''MyDatum [('MyDatum, 0)]
PlutusTx.makeIsDataIndexed ''MyRedeemer [('Spend, 0), ('Cancel, 1)]

Common Validation Patterns

Check Transaction Signer

{-# INLINABLE signedByOwner #-}
signedByOwner :: PubKeyHash -> TxInfo -> Bool
signedByOwner pkh info = pkh `elem` txInfoSignatories info

Check Time Range

{-# INLINABLE checkDeadline #-}
checkDeadline :: POSIXTime -> ScriptContext -> Bool
checkDeadline deadline ctx = 
    traceIfFalse "Deadline not reached" $ from deadline `contains` txInfoValidRange (scriptContextTxInfo ctx)

Check Token Transfer

{-# INLINABLE checkTokenTransfer #-}
checkTokenTransfer :: CurrencySymbol -> TokenName -> Integer -> PubKeyHash -> TxInfo -> Bool
checkTokenTransfer cs tn amt receiver info =
    traceIfFalse "Token transfer failed" $ 
        valueOf (valuePaidTo info receiver) cs tn >= amt

Check Datum Update

{-# INLINABLE checkDatumUpdate #-}
checkDatumUpdate :: ScriptContext -> (Datum -> Datum -> Bool) -> Bool
checkDatumUpdate ctx checkFn = case findOwnInput ctx of
    Nothing -> traceError "Input not found"
    Just input -> 
        let 
            inputDatum = getDatum $ txOutDatumHash $ txInInfoResolved input
            outputDatum = getOutputDatum $ getContinuingOutput ctx
        in
            traceIfFalse "Invalid datum update" $ checkFn inputDatum outputDatum

Plutus Contract Monad

Contract Endpoints

type MySchema =
    Endpoint "give" GiveParams
    .\/ Endpoint "grab" GrabParams

give :: AsContractError e => GiveParams -> Contract w s e ()
give params = do
    pkh <- pubKeyHash <$> ownPubKey
    let datum = MyDatum { ... }
        tx = mustPayToTheScript datum $ Ada.lovelaceValueOf amount
    ledgerTx <- submitTxConstraints typedValidator tx
    void $ awaitTxConfirmed $ txId ledgerTx
    logInfo @String $ "Created script output"

grab :: AsContractError e => GrabParams -> Contract w s e ()
grab params = do
    utxos <- utxosAt scrAddress
    -- Build and submit transaction to consume UTXOs

Error Handling

handleError :: Text -> Contract w s Text a -> Contract w s Text a
handleError msg action = catchError action $ \err -> do
    logError $ msg <> ": " <> show err
    throwError $ msg <> ": " <> show err

Minting Policies

Basic Minting Policy

{-# INLINABLE mkPolicy #-}
mkPolicy :: PubKeyHash -> () -> ScriptContext -> Bool
mkPolicy pkh _ ctx = 
    traceIfFalse "Not signed by token issuer" $ 
        txSignedBy (scriptContextTxInfo ctx) pkh

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

curSymbol :: PubKeyHash -> CurrencySymbol
curSymbol = scriptCurrencySymbol . policy

NFT Minting Policy

{-# INLINABLE mkNFTPolicy #-}
mkNFTPolicy :: TxOutRef -> TokenName -> () -> ScriptContext -> Bool
mkNFTPolicy 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 (txInfoMint info) of
        [(cs, tn', amt)] -> cs == ownCurrencySymbol ctx && tn' == tn && amt == 1
        _                -> False

Plutus Application Backend (PAB) Integration

PAB Contract Setup

endpoints :: Contract () MySchema Text ()
endpoints = selectList [give, grab] >> endpoints

mkSchemaDefinitions ''MySchema

myContract :: AsContractDefinition (Last MyContractState)
myContract = ContractDefinition
    { contractBehavior = endpoints
    , contractStartEnd = ISZ $ Last initialState
    }

$(mkKnownCurrencies [])

Testing with EmulatorTrace

Basic Emulator Test

testScenario :: EmulatorTrace ()
testScenario = do
    h1 <- activateContractWallet (Wallet 1) myContract
    h2 <- activateContractWallet (Wallet 2) myContract
    
    callEndpoint @"give" h1 GiveParams{...}
    void $ Emulator.waitNSlots 1
    
    callEndpoint @"grab" h2 GrabParams{...}
    void $ Emulator.waitNSlots 1

test :: IO ()
test = runEmulatorTraceIO testScenario

Testing with Property Tests

prop_ValidDatum :: MyDatum -> Property
prop_ValidDatum datum = 
    let encoded = fromBuiltinData (toBuiltinData datum)
    in case encoded of
        Nothing -> property False
        Just d' -> datum === d'

tests :: TestTree
tests = testGroup "MyContract"
    [ testProperty "datum roundtrip" prop_ValidDatum
    , testEmulator "successful give and grab" testScenario
    ]

Common Plutus Types

TypeDescriptionExample
PubKeyHashHash of a public key"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
ValidatorHashHash of a validator scriptScripts.validatorHash validator
AddressScript or pubkey addressscriptAddress validator
ValueTokens with amountsAda.lovelaceValueOf 5000000 <> assetClassValue token 1
AssetClassCurrency symbol + token nameAssetClass (curSymbol, tokenName)
POSIXTimeTime in millisecondsPOSIXTime 1643067600000
IntervalTime rangeinterval (POSIXTime 1000) (POSIXTime 2000)
DatumData attached to UTxODatum $ toBuiltinData myDatum
RedeemerInput to validatorRedeemer $ toBuiltinData myRedeemer

Common Plutus V2 Features

FeatureDescriptionUsage
Reference InputsUTxOs that are read but not consumedfindReferenceInput
Inline DatumsDatums stored directly in UTxOsOutputDatum (OutputDatumHash dh) vs OutputDatum (OutputInlineDatum d)
Reference ScriptsReusable validator scriptslookupReferenceScript
Plutus V2 ContextsEnhanced transaction contextimport Plutus.V2.Ledger.Contexts

Cardano CLI Commands for Plutus

Compile Plutus Script

cabal run plutus-example -- script --out-file my-validator.plutus

Create Datum/Redeemer Files

# Create datum JSON file
echo '{"constructor":0,"fields":[{"bytes":"deadbeef..."}]}' > datum.json

# Hash the datum
cardano-cli transaction hash-script-data --script-data-file datum.json

Transaction with Plutus Script

# Build transaction with Plutus script
cardano-cli transaction build \
  --alonzo-era \
  --testnet-magic 1 \
  --tx-in "txhash#txix" \
  --tx-in-script-file my-validator.plutus \
  --tx-in-datum-file datum.json \
  --tx-in-redeemer-file redeemer.json \
  --tx-in-collateral "txhash#txix" \
  --tx-out "addr+lovelace" \
  --change-address addr \
  --protocol-params-file pparams.json \
  --out-file tx.body

# Sign transaction
cardano-cli transaction sign \
  --tx-body-file tx.body \
  --signing-key-file payment.skey \
  --out-file tx.signed

# Submit transaction
cardano-cli transaction submit \
  --testnet-magic 1 \
  --tx-file tx.signed

Common Errors & Solutions

ErrorPossible CauseSolution
"ExUnitsError"Execution units exceededSimplify validator logic or increase protocol parameters
"InputsExhaustedError"UTxO not foundEnsure UTxO exists and is unspent
"ValidationError [... ] failed"Validation failedCheck validator logic and ensure conditions are met
"OutsideValidityIntervalError"Transaction outside validity rangeSet appropriate validity range or check POSIXTime calculations
"MissingDatumHashError"Missing datumEnsure datum is provided with correct hash
"NonOutputSupplimentaryDatums"Invalid datum usageCheck datum references and usage in transaction

Development Environment Setup

Project Template

# Clone Plutus Apps repository
git clone https://github.com/input-output-hk/plutus-apps.git

# Enter nix shell
cd plutus-apps
nix-shell

# Create new project using template
cookiecutter https://github.com/input-output-hk/plutus-starter

Build and Run

# Build project
cabal build

# Run tests
cabal test

# Run PAB
cabal run plutus-pab

Resources for Further Learning

Official Documentation

Community Resources

Tools


This cheatsheet provides a comprehensive overview of Cardano Plutus development. For specific use cases and examples, refer to the official documentation and community resources listed above.

Scroll to Top