Utilities Cookbook
This cookbook collects task-oriented recipes for the Hydra SDK utility namespaces, distilled from the real nodejs-playground/src implementations. Each recipe is a small, runnable snippet: a problem and the shortest code that solves it. Every namespace shown here is exported from @hydra-sdk/core. For full function signatures and parameter tables, see /api/utilities.
Building datums
Construct Plutus data with the DatumUtils encoders. Start with the primitives, then compose them into structured, nested datums.
import { DatumUtils } from '@hydra-sdk/core'
// Simple datum types
const intDatum = DatumUtils.mkInt(42)
const bytesDatum = DatumUtils.mkBytes('deadbeef')
const listDatum = DatumUtils.mkList([intDatum, bytesDatum])
// Constructor datum
const constrDatum = DatumUtils.mkConstr(0, [intDatum, bytesDatum])
// Map datum
const mapDatum = DatumUtils.mkMap([
[DatumUtils.mkBytes('key1'), intDatum],
[DatumUtils.mkBytes('key2'), bytesDatum]
])
Complex, nested datum
The production pattern below builds a deeply nested constructor with an inner map-of-maps. This is the canonical copy of this recipe.
buildDatum block is also used in the Mint and Burn Tokens guide, which links back here as the canonical source. Keep the two in sync.import { DatumUtils, ParserUtils } from '@hydra-sdk/core'
import { CardanoWASM } from '@hydra-sdk/cardano-wasm'
// Production datum builder pattern
const buildDatum = (key: string, l1Vkh: string, l2Vkh: string, amount: string) => {
const bKey = DatumUtils.mkBytes(key)
const cL1Vkh = DatumUtils.mkConstr(0, [DatumUtils.mkBytes(l1Vkh)])
const cL2Vkh = DatumUtils.mkConstr(0, [DatumUtils.mkBytes(l2Vkh)])
const constrKey = DatumUtils.mkConstr(0, [bKey, cL1Vkh, cL2Vkh])
const wrap1 = DatumUtils.mkConstr(0, [constrKey])
// Nested map: { "" => { "" => amount } }
const emptyBytes = DatumUtils.mkBytes('')
const mapVal = CardanoWASM.PlutusMapValues.new()
mapVal.add(DatumUtils.mkInt(amount))
const innerMap = DatumUtils.mkMap([[emptyBytes, mapVal]])
const outerMapVal = CardanoWASM.PlutusMapValues.new()
outerMapVal.add(innerMap)
const outerMap = DatumUtils.mkMap([[emptyBytes, outerMapVal]])
return DatumUtils.mkConstr(0, [wrap1, outerMap])
}
// Usage
const datum = buildDatum(
'ee91e90e791e4cd983d1b1f331d1e8eb',
'326cd6bff6114c4d14ebf2385883aac43c4e64476e6a47314f9b2003',
'f602ad4b16ec2e1a96989dc140eacf546359695cfece8510c8d1c0ac',
'4000000'
)
New datum encoders
import { DatumUtils, NETWORK_ID } from '@hydra-sdk/core'
// Lists
const list = DatumUtils.mkList([DatumUtils.mkInt(1), DatumUtils.mkInt(2)])
const bytesList = DatumUtils.mkBytesList(['deadbeef', 'cafe'])
const intList = DatumUtils.mkIntList([1, 2, 3])
// Booleans: False = Constr(0, []), True = Constr(1, [])
const flag = DatumUtils.mkBool(true)
// Option: Some = Constr(0, [v]), None = Constr(1, [])
const some = DatumUtils.mkOption(DatumUtils.mkInt(42))
const none = DatumUtils.mkOption()
// Output reference: Constr(0, [Bytes(txHash), Int(index)])
const outRef = DatumUtils.mkOutputRef({ txHash: 'abc123...', index: 0 })
// Address <-> PlutusData
const addrDatum = DatumUtils.mkAddress('addr_test1...')
const bech32 = DatumUtils.parseAddress(addrDatum, NETWORK_ID.PREPROD)
NFT metadata datum
Build a CIP-style metadata datum by hex-encoding every key and value.
import { DatumUtils, ParserUtils } from '@hydra-sdk/core'
const createNFTDatum = (name: string, image: string, attributes: Record<string, string>) => {
const attributeEntries = Object.entries(attributes).map(([key, value]) => [
DatumUtils.mkBytes(ParserUtils.stringToHex(key)),
DatumUtils.mkBytes(ParserUtils.stringToHex(value))
])
return DatumUtils.mkMap([
[DatumUtils.mkBytes(ParserUtils.stringToHex("name")),
DatumUtils.mkBytes(ParserUtils.stringToHex(name))],
[DatumUtils.mkBytes(ParserUtils.stringToHex("image")),
DatumUtils.mkBytes(ParserUtils.stringToHex(image))],
[DatumUtils.mkBytes(ParserUtils.stringToHex("attributes")),
DatumUtils.mkMap(attributeEntries)]
])
}
// Usage
const nftDatum = createNFTDatum(
"CryptoPunk #1234",
"ipfs://QmYourHashHere",
{ "type": "Alien", "accessory": "3D Glasses" }
)
Redeemers
Wrap PlutusData into a Redeemer with RedeemerUtils.
import { RedeemerUtils, DatumUtils } from '@hydra-sdk/core'
// Wrap PlutusData into a Redeemer (tag defaults to 'spend', index to 0)
const data = DatumUtils.mkConstr(1, [])
const redeemer = RedeemerUtils.mkRedeemer(data, { tag: 'spend', index: 0 })
// Tag-preset helpers
const spendRedeemer = RedeemerUtils.mkSpendRedeemer(data)
const mintRedeemer = RedeemerUtils.mkMintRedeemer(data)
// The "no argument" Unit redeemer: Constr(0, [])
const unitRedeemer = RedeemerUtils.mkUnitRedeemer()
// Execution units (placeholder budget — evaluate scripts for production)
const exUnits = RedeemerUtils.mkExUnits(RedeemerUtils.DEFAULT_EX_UNITS)
Time and slots
Convert between Unix time and slots for transaction validity ranges and deadlines with TimeUtils and SLOT_CONFIG_NETWORK.
import { TimeUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core'
// Convert Unix timestamp to slot
const currentSlot = TimeUtils.unixTimeToEnclosingSlot(
Date.now(),
SLOT_CONFIG_NETWORK.PREPROD
)
// Deadline calculation (24 hours from now)
const deadline = TimeUtils.unixTimeToEnclosingSlot(
Date.now() + (24 * 60 * 60 * 1000),
SLOT_CONFIG_NETWORK.PREPROD
)
// Convert slot back to Unix timestamp
const readableTime = TimeUtils.slotToBeginUnixTime(
currentSlot,
SLOT_CONFIG_NETWORK.PREPROD
)
console.log('Current slot:', currentSlot)
console.log('Deadline slot:', deadline)
console.log('Readable time:', new Date(readableTime))
Vesting schedule datum
Combine TimeUtils with DatumUtils to encode a start/end slot window.
import { TimeUtils, DatumUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core'
const createVestingDatum = (
beneficiary: string,
totalAmount: bigint,
vestingMonths: number
) => {
const slotConfig = SLOT_CONFIG_NETWORK.PREPROD
const startTime = Date.now() + (30 * 24 * 60 * 60 * 1000) // Start in 30 days
const endTime = startTime + (vestingMonths * 30 * 24 * 60 * 60 * 1000)
const startSlot = TimeUtils.unixTimeToEnclosingSlot(startTime, slotConfig)
const endSlot = TimeUtils.unixTimeToEnclosingSlot(endTime, slotConfig)
return DatumUtils.mkConstr(0, [
DatumUtils.mkBytes(beneficiary),
DatumUtils.mkInt(totalAmount),
DatumUtils.mkInt(startSlot),
DatumUtils.mkInt(endSlot)
])
}
Minting policies
Derive a native minting policy (and its policy ID) from a wallet address with PolicyUtils.
import { PolicyUtils, ParserUtils, Serializer } from '@hydra-sdk/core'
// Create policy from wallet address
const walletAddress = wallet.getAccount().baseAddressBech32
const scriptCborHex = PolicyUtils.buildMintingPolicyScriptFromAddress(walletAddress)
const policyId = PolicyUtils.policyIdFromNativeScript(scriptCborHex)
// Token name conversion
const assetNameHex = ParserUtils.stringToHex('MyToken')
const assetUnit = Serializer.serializeAssetUnit(policyId, assetNameHex)
console.log('Policy ID:', policyId)
console.log('Asset Unit:', assetUnit)
Data conversion
Convert strings, bytes, and asset units, and read amounts back out of a transaction.
import { ParserUtils } from '@hydra-sdk/core'
// String to hex for token names
const assetNameHex = ParserUtils.stringToHex('AniaToken')
// Bytes to hex round trip
const bytes = new Uint8Array([0xde, 0xad, 0xbe, 0xef])
const hex = ParserUtils.bytesToHex(bytes)
const backToBytes = ParserUtils.hexToBytes(hex)
console.log('Round trip successful:', bytes.every((b, i) => b === backToBytes[i]))
Asset unit serialization
Serialize a policyId + assetName into an asset unit, and deserialize it back.
import { ParserUtils, Serializer, Deserializer } from '@hydra-sdk/core'
const processAssetUnit = (policyId: string, assetName: string) => {
// Serialize asset unit
const assetNameHex = ParserUtils.stringToHex(assetName)
const assetUnit = Serializer.serializeAssetUnit(policyId, assetNameHex)
// Deserialize back
const { policyId: deserializedPolicy, assetName: deserializedName } =
Deserializer.deserializeAssetUnit(assetUnit)
return {
original: { policyId, assetName },
serialized: assetUnit,
deserialized: {
policyId: deserializedPolicy,
assetName: ParserUtils.hexToString(deserializedName)
}
}
}
const assetData = processAssetUnit('abc123...', 'MyToken')
Read all amounts from a transaction
deserializeAmountsFromTx sums every output's amounts, merged by unit.import { Deserializer } from '@hydra-sdk/core'
// Sum every output's amounts, merged by unit
const amounts = Deserializer.deserializeAmountsFromTx(signedTxCbor)
console.log('Total amounts:', amounts)
Providers
Create a chain data provider for fetching UTxOs and submitting transactions. All providers expose the same .fetcher / .submitter surface.
await bridge.getProtocolParameters().import { ProviderUtils } from '@hydra-sdk/core'
// Create Blockfrost provider
const blockfrostProvider = new ProviderUtils.BlockfrostProvider({
apiKey: process.env.BLOCKFROST_PROJECT_ID || '',
network: 'preprod'
})
// Create Ogmios provider
const ogmiosProvider = new ProviderUtils.OgmiosProvider({
network: 'preprod',
apiEndpoint: 'http://localhost:1337'
})
// Fetch UTxOs for an address through the provider's fetcher
const utxos = await blockfrostProvider.fetcher.fetchAddressUTxOs(address)
// Submit a signed transaction through the provider's submitter
const txHash = await ogmiosProvider.submitter.submitTx(signedTxCbor)
Demeter provider
DemeterProvider extends BlockfrostProvider; same .fetcher / .submitter surface.import { ProviderUtils } from '@hydra-sdk/core'
const demeterProvider = new ProviderUtils.DemeterProvider({
authToken: process.env.DEMETER_AUTH_TOKEN || '',
network: 'preprod'
})
const utxos = await demeterProvider.fetcher.fetchAddressUTxOs(address)
Validation
Validate transaction outputs and addresses before building a transaction with ValidationUtils and AddressUtils.
import { ValidationUtils, AddressUtils } from '@hydra-sdk/core'
// Validate a transaction output shape
const okOutput = ValidationUtils.isValidTxOutput({
address: 'addr_test1...',
amount: [{ unit: 'lovelace', quantity: '2000000' }]
})
// Validate an address (type: 'bech32' | 'hex' | 'bytes', default 'bech32')
const okAddress = AddressUtils.isValidAddress('addr_test1...')
// Extract the payment key hash (returns null if not derivable)
const pubKeyHash = AddressUtils.getPubkeyHashFromAddress('addr_test1...')
Error handling
Utility calls can throw on malformed input. Wrap them so a failure returns null (or a fallback) instead of crashing the flow.
import { ParserUtils, DatumUtils } from '@hydra-sdk/core'
const safeConversion = (input: string, type: 'hex' | 'datum') => {
try {
switch (type) {
case 'hex':
return ParserUtils.stringToHex(input)
case 'datum':
return DatumUtils.mkBytes(ParserUtils.stringToHex(input))
default:
throw new Error('Unsupported conversion type')
}
} catch (error) {
console.error(`Conversion failed for ${type}:`, error)
return null
}
}
// Usage with fallback
const result = safeConversion('test data', 'hex') || 'default_hex_value'
try/catch wrapper works for any utility call — for example wrap TimeUtils.unixTimeToEnclosingSlot(...) to guard against invalid timestamps.See also
- /api/utilities — full function signatures for every utility namespace.
- /guides/transactions — build and submit transactions using these utilities.
- /guides/mint-burn-tokens — end-to-end minting flow that reuses the
buildDatumrecipe above.
