I am trying to find a way to inspect the base16 hash from a Tx on-chain using plutus.

I get a BuiltinByteString from the txInputs here:

getTxIdBBS :: TxInInfo -> BuiltinByteString
getTxIdBBS txInfo' = getTxId(txOutRefId (txInInfoOutRef txInfo'))

but then I need to get the base16 representation so I can compare and inspect the hex digits.     Ideally, I would like to use a function similar to below, but this only works off-chain (due to conversion to non-builtin).

import qualified Data.ByteString.Base16 as B16 (encode)

txId    = getTxIdBBS (txInfo)
txId'   = fromBuiltin(txId)
txIdHex = toBuiltin(B16.encode txId')

I've also tried the following, but this does not work either.

import Data.Text.Encoding qualified as E
import PlutusTx.Builtins.Internal qualified as BI

{-# INLINABLE txEncodeUtf16 #-}
txEncodeUtf16 :: BI.BuiltinString -> BI.BuiltinByteString
txEncodeUtf16 (BI.BuiltinString s) = BI.BuiltinByteString $ E.encodeUtf16BE s

txHash = txEncodeUtf16(decodeUtf8(txId))

I am not able to use decodeUtf8 on a BuiltinByteString generated by sha2_256 and get the following error:

decodeUtf8*** Exception: Cannot decode byte '\xc6': Data.Text.Internal.Encoding.decodeUtf8: Invalid UTF-8 stream

I also can’t use encodeUtf16BE due to the following compiler error:

GHC Core to PLC plugin: E042:Error: Unsupported feature: Type constructor: GHC.Prim.ByteArray#

Any help or suggestions on how to convert the BuiltinByteString from getTxId into a BuiltinString  "on-chain" where I can inspect the hex digits would be awesome.




3 Answers 3


I was able to resolve this issue by converting (encoding) the BuiltinByteString from getTxId into a hex format. From there I was able to inpsect the hex digits. Below is an onchain encodeHex function that I used (thanks Minswap!).

-- Convert from a byte string to its hex (base16) representation. Example: [2, 14, 255] => "020eff"
{-# INLINEABLE encodeHex #-}
encodeHex :: BuiltinByteString -> BuiltinByteString
encodeHex input = go 0
    len = lengthOfByteString input
    go :: Integer -> BuiltinByteString
    go i
      | i == len = emptyByteString
      | otherwise =
        consByteString (toChar $ byte `quotient` 16) $
          consByteString (toChar $ byte `remainder` 16) (go $ i + 1)
        byte = indexByteString input i

        toChar :: Integer -> Integer
        toChar x
          -- 48 is ASCII code for '0'
          | x < 10 = x + 48
          -- 97 is ASCII code for 'a'
          -- x - 10 + 97 = x + 87
          | otherwise = x + 87

You might just need to use the fromBuiltin fn to get a ByteString:

import Data.ByteString.Char8 as C
import Prelude as Haskell

converted :: BuiltinByteString -> BuiltinString
converted bbs' = let b = C.unpack( fromBuiltin bbs' ) :: Haskell.String
                              -- in toBuiltin b
                              in stringToBuiltinString b 

Update: I'm not sure I've figured out how to set up the toBuiltin fn. It's not compiling in my test project.

Update: the actual fn is stringToBuiltinString

  • 1
    This does not work on-chain
    – Mateus
    Apr 1, 2022 at 12:45
  • @Mateus is correct, this doesn't work onchain. I'll add another answer with what I've found.
    – marcel_g
    May 2, 2022 at 4:59

but then I need to get the base16 representation so I can compare and inspect the hex digits.

Not sure this is correct, I was able to inspect the txId directly with getTxId, take a look at the following code

import           Data.Aeson           hiding (Value)
import           GHC.Generics
import           Ledger               hiding (singleton)
import           Ledger.Scripts
import           Ledger.Typed.Scripts as Scripts
import           Ledger.Value
import           Plutus.ChainIndex
import qualified PlutusTx
import PlutusTx.Builtins
import qualified PlutusTx.AssocMap    as PlutusMap
import           PlutusTx.Prelude
import qualified PlutusTx.Ratio       as R
import qualified Prelude


{-# INLINEABLE mkNFTPolicy #-}
mkNFTPolicy :: Integer -> TxOutRef -> ScriptContext -> Bool
mkNFTPolicy size oref ctx = 
    nftPolicyTraceIfFalse "UTxO not consumed" hasUTxO &&
    nftPolicyTraceIfFalse "Invalid token name or amount" validMinting
    refId :: BuiltinByteString
    refId = case intToBuiltinByteString $ txOutRefIdx oref of
        bbs | lengthOfByteString bbs == 1 -> "0" `appendByteString` bbs
            | lengthOfByteString bbs == 2 -> bbs
        _ -> nftPolicyTraceError "Invalid ref id"

    validTokenName :: TokenName -> Bool
    validTokenName (TokenName bs) = sliceByteString 0 (size + 2) bs == expected
        expected :: BuiltinByteString
        expected = sliceByteString 0 size (getTxId $ txOutRefId oref) `appendByteString` refId
  • 1
    Thanks Mateus. I was able to resolve the issue, I needed to convert (encode) the onchain reprentation proivded by getTxId into a hex format. From there I was able to look at each of the hex digits. I'll post the encodeHex function on the main thread.
    – lley154
    Apr 4, 2022 at 20:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.