When I use scriptAddress on a validator of my SC I got the following result:

Address {addressCredential = ScriptCredential 22261390d0b365fef5a12bc33a29987fe10b28ed58e1036d27b0db43c613edad, addressStakingCredential = Nothing}

Is 22261390d0b365fef5a12bc33a29987fe10b28ed58e1036d27b0db43c613edad the script address?

Why doesn't it start with addr_test1?

3 Answers 3


The piece of data you show is encoded in Base16. Addresses are however typically encoded as Bech32 which allows for defining human readable prefixes like "addr_test".

As a matter of fact, it is possible to encode the same underlying data payload in different formats. The preferred one for presenting the data to end users should be Bech32, but Base16 may be a sensible choice for interoperability in contexts like command-lines or developer tools since it has wider support than Bech32.

  • And how to convert this address to bech32 in haskell?
    – martin
    Commented Nov 4, 2021 at 18:11
  • hackage.haskell.org/package/bech32
    – KtorZ
    Commented Nov 5, 2021 at 20:31
  • This does not actually encode it correctly. When using the CLI, the address output of both is different: ScriptCredential /= base16 representation of cardano cli address
    – martin
    Commented Nov 12, 2021 at 21:28
  • That is because a script credential isn't an address. Addresses have a header for type and discrimination; have a look at: github.com/cardano-foundation/CIPs/tree/master/CIP-0019
    – KtorZ
    Commented Nov 14, 2021 at 8:24

The sriptAddress is in Base16 (aka Hexadecimal)

You need to do a few things to it to transform into an address: Add "70" to the front for Testnet, or "71" for Mainnet

The Header for Testnet is "70" and for Mainnet "71" To work out why you need to go read CIP19: https://cips.cardano.org/cips/cip19/#shelley-addresses

And then look up the Header bits '011100000' for Testnet or Header bits '011100001' for Mainnet In a binary to Hex table, such as here: https://www.rapidtables.com/convert/number/binary-to-ascii.html

Then you need to convert it into Bech32 and then further add "addr_test" to the front for Testnet, or "addr" for Mainnet. This is best all done with the help of some libraries.

Here is a sample script that does this, and as a bonus also gives the CBORHex of the script

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications  #-}
{-# LANGUAGE TypeFamilies      #-}

module Test where

-- Haskell Imports
import Prelude hiding (($))
import Data.Maybe
import Codec.Serialise (serialise)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString as B
import qualified Data.ByteString.Short as SBS
import qualified Data.ByteString.Base16 as Base16
import qualified Data.Text as T
import Codec.Binary.Bech32 as B32
import Data.Text.Encoding as TE
import Data.Aeson.Encode.Pretty (Config (..), defConfig, encodePretty', keyOrder)

-- Plutus Imports
import Cardano.Api (serialiseToTextEnvelope)
import Cardano.Api.Shelley (PlutusScript (..), PlutusScriptV1)
import qualified Ledger hiding (singleton)
import qualified Plutus.V1.Ledger.Scripts as Plutus
import Plutus.V1.Ledger.Address (scriptHashAddress, Address)
import PlutusTx
import PlutusTx.Prelude hiding (Semigroup (..), unless, (.))
import qualified Cardano.Binary as CBOR
import Cardano.Codec.Bech32.Prefixes (addr_test, addr)

-- Create Validator
{-# INLINABLE mkValidator #-}
mkValidator :: BuiltinData -> BuiltinData -> BuiltinData -> ()
mkValidator _ _ _ = ()

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

-- Create PlutusScript from validator
-- Print the CBORHex
script :: Plutus.Script
script = Plutus.unValidatorScript validator

scrAddress :: Ledger.Address
scrAddress = Ledger.scriptAddress validator

scriptShortBs :: SBS.ShortByteString
scriptShortBs = (SBS.toShort . LBS.toStrict . serialise) script

srlScript :: PlutusScript PlutusScriptV1
srlScript = PlutusScriptSerialised scriptShortBs

scriptCBORHex :: B.ByteString
scriptCBORHex = Base16.encode $ CBOR.serialize' srlScript

-- Generate JSON format
-- for Validator CBORHex

textEnvelopeJSONKeyOrder :: T.Text -> T.Text -> Ordering
textEnvelopeJSONKeyOrder = keyOrder ["type", "description", "cborHex"]

textEnvelopeJSONConfig :: Config
textEnvelopeJSONConfig = defConfig { confCompare = textEnvelopeJSONKeyOrder }

textEnveloper = encodePretty' textEnvelopeJSONConfig (serialiseToTextEnvelope Nothing srlScript) <> "\n"

-- Generate PlutusScript Hash
validatorHash :: Plutus.ValidatorHash
validatorHash = fromJust (Ledger.toValidatorHash scrAddress)

validatorHashHex :: String
validatorHashHex = show validatorHash

-- Create Script Address

addHeader :: String -> String -> T.Text
addHeader h v = T.pack (h Prelude.++ v)

Header for Testnet is "70" and for Mainnet "71"
To work out why you need to go read CIP19:

And then look up the
- Header Bits '011100000' for Testnet or 
- Header Bits '011100001' for Mainnet
In a binary to Hex table, such as here:

The header is in Base16 format (aka Hexadecimal)

header :: String
header = "70"

validatorHashHexWHeader :: B.ByteString
validatorHashHexWHeader = TE.encodeUtf8 (addHeader header validatorHashHex)

binary :: B.ByteString
Right binary = Base16.decode validatorHashHexWHeader

Prefix for Testnet: "addr_test" and Mainnet: "addr"
These prefixes are imported from Cardano.Codec.Bech32.Prefixes

dataPart :: B32.DataPart
dataPart = B32.dataPartFromBytes binary

The bech32 representation is derived in 2 steps:
- bech32 = prefix + dataPart
- and where dataPart = header + validatorHash
addrBech32 :: T.Text
Right addrBech32 = B32.encode addr_test dataPart

main :: IO ()
main = do
    putStrLn ("Script CBORHex: " Prelude.++ show scriptCBORHex)
    putStrLn ("Script Addr: " Prelude.++ show addrBech32)

Because plutus uses (Template) Haskell, hence deterministic.

Your SC is going to run as is when it goes live. Every output you get while developing your SC on the testnet/pc is applicable when you eventually move it to the mainnet.

So, it becomes impractical to expect a different output during development and a different one in production.

  • I dont think you understood my question. On Alonzo testnet all script addresses start with addr_test1 but the string inside the Address struct does not have that format? What is this string then? Commented Aug 22, 2021 at 6:26

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.