user326: I'd like to write a policy script that receives a custom data type for the Redeemer argument, but does not use Plutus.liftCode to "bake the argument value" into the compiled Plutus script.  Instead, I want to be able to compile the policy script with no redeemer value upfront and just declare the type of the argument (e.g. my custom data type) that will be received when the transaction is executed.  This will mean that the custom redeemer data will be submitted with the transaction instead.
There are examples of this for Validator, e.g. TypedValidator example from PPP, but I cannot figure out how to do the equivalent thing for a PolicyScript.  All the policy script examples I've seen use Plutus.liftCode.
Here's the starting point of the code I'm trying to write.  I put brackets around the places in the code that I need an equivalent idea for PolicyScript.
data CustomRedeemer = CustomRedeemer
    { txOut :: TxOutRef
    , nftName :: String
    } deriving Show

PlutusTx.unstableMakeIsData ''CustomRedeemer

{-# INLINABLE mkNftPolicy #-}
mkNftPolicy :: CustomRedeemer -> ScriptContext -> Bool
mkNftPolicy custom ctx = traceIfFalse "UTxO not consumed"   hasUTxO           &&
                         traceIfFalse "wrong amount minted" checkMintedAmount
  where (...)

data MyTypedInstance
instance <equivalent-of: Scripts.ValidatorTypes> MyTypedInstance where
    type instance RedeemerType MyTypedInstance = CustomRedeemer

typedPolicy :: <equivalent-of: Scripts.TypedValidator> MyTypedInstance
typedPolicy =  <equivalent-of: Scripts.mkTypedValidator> @MyTypedInstance
    $$(PlutusTx.compile [|| mkNftPolicy ||])
    $$(PlutusTx.compile [|| wrap ||])
  where
    wrap = <equivalent-of: Scripts.wrapValidator> @CustomRedeemer


Thanks!

user6113: It's pretty simple:

Define the redeemer with the proper types (they must be part of UnsafeFromData typeclass in order to use PlutusTx.unstableMakeIsData (2))

Do PlutusTx.unstableMakeIsData ''CustomRedeemer.

Define the policy with mkMintingPolicyScript $$(PlutusTx.compile [|| Scripts.wrapMintingPolicy mkPolicy ||]) where mkPolicy doesn't have any lifted parameters.

Have fun.


Important: If you define the custom redeemer along with PlutusTx.unstableMakeIsData in the same module as your minting policy script, the compiler complains in Scripts.wrapMintingPolicy saying that PlutusTx.UnsafeFromData CustomRedeemer must be imported, not defined locally. SOLUTION: Define it in a separate module. This must be Template Haskell stuff...

Additionally, I share a simple example of all of this:
[custom redeemer types module]
{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE NoImplicitPrelude   #-}

module Week05.WithCustomRedeemerTypes where

import           PlutusTx.Prelude
import           Prelude          (Show (..))
import qualified PlutusTx
import           Ledger           (TokenName)

data CustomRedeemer = CustomRedeemer
    { nftName     :: TokenName 
    , description :: BuiltinByteString
    } deriving Show

PlutusTx.unstableMakeIsData ''CustomRedeemer 

[minting policy module]
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE DeriveAnyClass      #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE NoImplicitPrelude   #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE TypeApplications    #-}
{-# LANGUAGE TypeFamilies        #-}
{-# LANGUAGE TypeOperators       #-}

module Week05.WithCustomRedeemer where

import           Control.Monad          hiding (fmap)
import           Data.Aeson             (ToJSON, FromJSON)
import           Data.Text              (Text)
import           GHC.Generics           (Generic)
import           Plutus.Contract        as Contract
import           Plutus.Trace.Emulator  as Emulator
import qualified PlutusTx
import           PlutusTx.Prelude       hiding (Semigroup(..), unless)
import           Ledger                 hiding (mint, singleton)
import           Ledger.Constraints     as Constraints
import qualified Ledger.Typed.Scripts   as Scripts
import           Ledger.Value           as Value
import           Prelude                (IO, Show (..), String)
import           Text.Printf            (printf)
import           Wallet.Emulator.Wallet

import           Week05.WithCustomRedeemerTypes
 
{-# INLINABLE mkNftPolicy #-}
mkNftPolicy :: CustomRedeemer -> ScriptContext -> Bool
mkNftPolicy CustomRedeemer{nftName} _ = 
    let expected = TokenName "kurt"
    in traceIfFalse "Kurt is sad because the nft is not called kurt :(" $ nftName == expected 

policy :: Scripts.MintingPolicy
policy = mkMintingPolicyScript $$(PlutusTx.compile [|| Scripts.wrapMintingPolicy mkNftPolicy ||])

curSymbol :: CurrencySymbol
curSymbol = scriptCurrencySymbol policy


data MintParams = MintParams
    { mpTokenName :: !TokenName
    , mpAmount    :: !Integer
    } deriving (Generic, ToJSON, FromJSON)

type FreeSchema = Endpoint "mint" MintParams

mint :: MintParams -> Contract w FreeSchema Text ()
mint mp = do
    let tn = mpTokenName mp
        redeemer = Redeemer $ PlutusTx.toBuiltinData $ CustomRedeemer tn "a description"
        val      = Value.singleton curSymbol (mpTokenName mp) (mpAmount mp)
        lookups  = Constraints.mintingPolicy policy
        tx       = Constraints.mustMintValueWithRedeemer redeemer val
    ledgerTx <- submitTxConstraintsWith @Scripts.Any lookups tx
    void $ awaitTxConfirmed $ getCardanoTxId ledgerTx
    Contract.logInfo @String $ printf "forged %s" (show val)

endpoints :: Contract () FreeSchema Text ()
endpoints = mint' >> endpoints
  where
    mint' = awaitPromise $ endpoint @"mint" mint

happyKurt :: IO ()
happyKurt = runEmulatorTraceIO $ do
    let tn = "kurt"
    h1 <- activateContractWallet (knownWallet 1) endpoints
    callEndpoint @"mint" h1 $ MintParams
        { mpTokenName = tn
        , mpAmount    = 555
        }
    void $ Emulator.waitNSlots 1

sadKurt :: IO ()
sadKurt = runEmulatorTraceIO $ do
    let tn = "bob"
    h1 <- activateContractWallet (knownWallet 1) endpoints
    callEndpoint @"mint" h1 $ MintParams
        { mpTokenName = tn
        , mpAmount    = 555
        }
    void $ Emulator.waitNSlots 1   


You can find the previous code integrated with week05 in my plutus-pioneer-program repo:
https://github.com/kindofdev/plutus-pioneer-program-iter3/blob/main/code/week05/src/Week05/WithCustomRedeemer.hs

326: <p>I'd like to write a policy script that receives a custom data type for the <code>Redeemer</code> argument, but does not use <code>Plutus.liftCode</code> to &quot;bake the argument value&quot; into the compiled Plutus script.  Instead, I want to be able to compile the policy script with no redeemer value upfront and just declare the type of the argument (e.g. my custom data type) that will be received when the transaction is executed.  This will mean that the custom redeemer data will be submitted with the transaction instead.</p>
<p>There are examples of this for Validator, e.g. <a href="https://github.com/input-output-hk/plutus-pioneer-program/blob/main/code/week02/src/Week02/IsData.hs" rel="nofollow noreferrer">TypedValidator example from PPP</a>, but I cannot figure out how to do the equivalent thing for a PolicyScript.  All the policy script examples I've seen use <code>Plutus.liftCode</code>.</p>
<p>Here's the starting point of the code I'm trying to write.  I put brackets around the places in the code that I need an equivalent idea for PolicyScript.</p>
<pre><code>data CustomRedeemer = CustomRedeemer
    { txOut :: TxOutRef
    , nftName :: String
    } deriving Show

PlutusTx.unstableMakeIsData ''CustomRedeemer

{-# INLINABLE mkNftPolicy #-}
mkNftPolicy :: CustomRedeemer -&gt; ScriptContext -&gt; Bool
mkNftPolicy custom ctx = traceIfFalse &quot;UTxO not consumed&quot;   hasUTxO           &amp;&amp;
                         traceIfFalse &quot;wrong amount minted&quot; checkMintedAmount
  where (...)

data MyTypedInstance
instance &lt;equivalent-of: Scripts.ValidatorTypes&gt; MyTypedInstance where
    type instance RedeemerType MyTypedInstance = CustomRedeemer

typedPolicy :: &lt;equivalent-of: Scripts.TypedValidator&gt; MyTypedInstance
typedPolicy =  &lt;equivalent-of: Scripts.mkTypedValidator&gt; @MyTypedInstance
    $$(PlutusTx.compile [|| mkNftPolicy ||])
    $$(PlutusTx.compile [|| wrap ||])
  where
    wrap = &lt;equivalent-of: Scripts.wrapValidator&gt; @CustomRedeemer

</code></pre>
<p>Thanks!</p>
 6113: <p>It's pretty simple:</p>
<ol>
<li><p>Define the redeemer with the proper types (they must be part of <strong>UnsafeFromData</strong> typeclass in order to use <strong>PlutusTx.unstableMakeIsData</strong> (2))</p>
</li>
<li><p>Do <code>PlutusTx.unstableMakeIsData ''CustomRedeemer</code>.</p>
</li>
<li><p>Define the policy with <code>mkMintingPolicyScript $$(PlutusTx.compile [|| Scripts.wrapMintingPolicy mkPolicy ||])</code> where <strong>mkPolicy</strong> doesn't have any lifted parameters.</p>
</li>
<li><p>Have fun.</p>
</li>
</ol>
<p><strong>Important</strong>: If you define the custom redeemer along with <strong>PlutusTx.unstableMakeIsData</strong> in the same module as your minting policy script, the compiler complains in <strong>Scripts.wrapMintingPolicy</strong> saying that <strong>PlutusTx.UnsafeFromData CustomRedeemer</strong> must be imported, not defined locally. SOLUTION: Define it in a separate module. This must be Template Haskell stuff...</p>
<p><a href="https://i.stack.imgur.com/7MT8P.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/7MT8P.png" alt="enter image description here" /></a></p>
<p>Additionally, I share a simple example of all of this:</p>
<p>[custom redeemer types module]</p>
<pre><code>{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE NoImplicitPrelude   #-}

module Week05.WithCustomRedeemerTypes where

import           PlutusTx.Prelude
import           Prelude          (Show (..))
import qualified PlutusTx
import           Ledger           (TokenName)

data CustomRedeemer = CustomRedeemer
    { nftName     :: TokenName 
    , description :: BuiltinByteString
    } deriving Show

PlutusTx.unstableMakeIsData ''CustomRedeemer 
</code></pre>
<p>[minting policy module]</p>
<pre><code>{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE DeriveAnyClass      #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE NoImplicitPrelude   #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE TypeApplications    #-}
{-# LANGUAGE TypeFamilies        #-}
{-# LANGUAGE TypeOperators       #-}

module Week05.WithCustomRedeemer where

import           Control.Monad          hiding (fmap)
import           Data.Aeson             (ToJSON, FromJSON)
import           Data.Text              (Text)
import           GHC.Generics           (Generic)
import           Plutus.Contract        as Contract
import           Plutus.Trace.Emulator  as Emulator
import qualified PlutusTx
import           PlutusTx.Prelude       hiding (Semigroup(..), unless)
import           Ledger                 hiding (mint, singleton)
import           Ledger.Constraints     as Constraints
import qualified Ledger.Typed.Scripts   as Scripts
import           Ledger.Value           as Value
import           Prelude                (IO, Show (..), String)
import           Text.Printf            (printf)
import           Wallet.Emulator.Wallet

import           Week05.WithCustomRedeemerTypes
 
{-# INLINABLE mkNftPolicy #-}
mkNftPolicy :: CustomRedeemer -&gt; ScriptContext -&gt; Bool
mkNftPolicy CustomRedeemer{nftName} _ = 
    let expected = TokenName &quot;kurt&quot;
    in traceIfFalse &quot;Kurt is sad because the nft is not called kurt :(&quot; $ nftName == expected 

policy :: Scripts.MintingPolicy
policy = mkMintingPolicyScript $$(PlutusTx.compile [|| Scripts.wrapMintingPolicy mkNftPolicy ||])

curSymbol :: CurrencySymbol
curSymbol = scriptCurrencySymbol policy


data MintParams = MintParams
    { mpTokenName :: !TokenName
    , mpAmount    :: !Integer
    } deriving (Generic, ToJSON, FromJSON)

type FreeSchema = Endpoint &quot;mint&quot; MintParams

mint :: MintParams -&gt; Contract w FreeSchema Text ()
mint mp = do
    let tn = mpTokenName mp
        redeemer = Redeemer $ PlutusTx.toBuiltinData $ CustomRedeemer tn &quot;a description&quot;
        val      = Value.singleton curSymbol (mpTokenName mp) (mpAmount mp)
        lookups  = Constraints.mintingPolicy policy
        tx       = Constraints.mustMintValueWithRedeemer redeemer val
    ledgerTx &lt;- submitTxConstraintsWith @Scripts.Any lookups tx
    void $ awaitTxConfirmed $ getCardanoTxId ledgerTx
    Contract.logInfo @String $ printf &quot;forged %s&quot; (show val)

endpoints :: Contract () FreeSchema Text ()
endpoints = mint' &gt;&gt; endpoints
  where
    mint' = awaitPromise $ endpoint @&quot;mint&quot; mint

happyKurt :: IO ()
happyKurt = runEmulatorTraceIO $ do
    let tn = &quot;kurt&quot;
    h1 &lt;- activateContractWallet (knownWallet 1) endpoints
    callEndpoint @&quot;mint&quot; h1 $ MintParams
        { mpTokenName = tn
        , mpAmount    = 555
        }
    void $ Emulator.waitNSlots 1

sadKurt :: IO ()
sadKurt = runEmulatorTraceIO $ do
    let tn = &quot;bob&quot;
    h1 &lt;- activateContractWallet (knownWallet 1) endpoints
    callEndpoint @&quot;mint&quot; h1 $ MintParams
        { mpTokenName = tn
        , mpAmount    = 555
        }
    void $ Emulator.waitNSlots 1   

</code></pre>
<p>You can find the previous code integrated with week05 in my <strong>plutus-pioneer-program</strong> repo:</p>
<p><a href="https://github.com/kindofdev/plutus-pioneer-program-iter3/blob/main/code/week05/src/Week05/WithCustomRedeemer.hs" rel="nofollow noreferrer">https://github.com/kindofdev/plutus-pioneer-program-iter3/blob/main/code/week05/src/Week05/WithCustomRedeemer.hs</a></p>
