API Reference

@hydra-sdk/transaction

Advanced transaction building and management for Cardano

Classes


TxBuilder

Main class for building and managing Cardano transactions with comprehensive support for all Cardano features.

TxBuilder.constructor(options?: TxBuilderOptions) → TxBuilder

Initializes a new transaction builder.

Parameters:

  • options? (TxBuilderOptions): Configuration options
    • fetcher?: Custom fetcher
    • submitter?: Custom submitter
    • evaluator? (IEvaluator): Optional script evaluator. When supplied and the transaction has Plutus redeemers, complete() runs a second build pass to write real exUnits back into the redeemers so the fee is accurate.
    • txEvaluationMultiplier? (number): Safety multiplier applied to evaluated exUnits before writing them back (e.g. 1.1 to over-provision by 10%). Default: 1.
    • isHydra?: Allows creating transactions for Hydra Head
    • params?: Custom protocol parameters
    • verbose?: Enable verbose mode
    • errorLogger?: Strict/testing mode — throws instead of continuing when tx building hits an issue (default: false; not recommended for production)

Example:

import { TxBuilder } from '@hydra-sdk/transaction'

// Create a basic builder
const txBuilder = new TxBuilder()

// With custom protocol parameters
const txBuilder = new TxBuilder({
  params: {
    minFeeA: 44,
    minFeeB: 155381,
    maxTxSize: 16384
  }
})

// For Hydra transactions
const txBuilder = new TxBuilder({
  isHydra: true,
  params: protocolParams,
  verbose: true
})

Returns: TxBuilder
A new TxBuilder instance


TxBuilder.setInputs(utxos: UTxO[], options?: { strategy: CoinSelectionStrategy }) → TxBuilder

Provide the UTxOs the builder may spend and choose the coin-selection strategy. The builder selects which of these UTxOs to consume when it completes the transaction. This is the public entry point for coin selection.

Parameters:

  • utxos (UTxO): List of available UTxOs
  • options? ({ strategy: CoinSelectionStrategy }): Coin-selection options. Default: { strategy: 'LargestFirstMultiAsset' }
    • LargestFirst: Select largest coins first
    • RandomImprove: Random selection with improvement
    • LargestFirstMultiAsset: Multi-asset friendly (default)
    • RandomImproveMultiAsset: Multi-asset friendly random

Example:

const utxos = await wallet.queryUtxos()

// Default strategy (LargestFirstMultiAsset)
const tx1 = await txBuilder.setInputs(utxos).complete()

// Specific strategy
const tx2 = await txBuilder.setInputs(utxos, { strategy: 'RandomImprove' }).complete()

// Restrict selection to specific UTxOs
const specificUtxos = utxos.filter(utxo =>
  utxo.output.amount.some(asset => asset.unit !== 'lovelace')
)
const tx3 = await txBuilder.setInputs(specificUtxos).complete()

Returns: TxBuilder
Returns the builder for method chaining


TxBuilder.txIn(txHash: string, outputIndex: number, amount?: Asset[], address?: string) → TxBuilder

Add a transaction input.

Parameters:

  • txHash (string): Transaction hash
  • outputIndex (number): Output index
  • amount? (Asset): Asset amounts (optional)
  • address? (string): Address (optional)

Example:

txBuilder.txIn(
  'a1b2c3d4e5f6...',
  0,
  [{ unit: 'lovelace', quantity: '2000000' }],
  'addr_test1...'
)

Returns: TxBuilder


TxBuilder.txOut(address: string, amount: Asset[]) → TxBuilder

Add a transaction output.

Parameters:

  • address (string): Recipient address
  • amount (Asset): List of assets

Example:

// ADA only
txBuilder.txOut('addr_test1...', [
  { unit: 'lovelace', quantity: '2000000' }
])

// Multi-asset
txBuilder.txOut('addr_test1...', [
  { unit: 'lovelace', quantity: '2000000' },
  { unit: 'policyId.assetName', quantity: '100' }
])

Returns: TxBuilder


TxBuilder.changeAddress(address: string) → TxBuilder

Set the change address.

Parameters:

  • address (string): Change address

Example:

const account = wallet.getAccount(0, 0)
txBuilder.changeAddress(account.baseAddressBech32)

Returns: TxBuilder


Convenience aliases — addOutput, setChangeAddress, …

Object-form aliases used throughout the guides. They are thin wrappers over txOut / changeAddress (identical behaviour) — pick whichever style you prefer.

AliasEquivalent to
addOutput({ address, amount })txOut(address, amount)
addOutputs(outputs: TxOutput[])one txOut per output
addLovelaceOutput(address, lovelace)txOut(address, [{ unit: 'lovelace', quantity: lovelace }])
setChangeAddress(address)changeAddress(address)
// These two builders produce the same transaction:
txBuilder.addOutput({ address, amount: [{ unit: 'lovelace', quantity: '2000000' }] }).setChangeAddress(addr)
txBuilder.txOut(address, [{ unit: 'lovelace', quantity: '2000000' }]).changeAddress(addr)

TxOutput is { address: string; amount: Asset[] }.


TxBuilder.mint(quantity: string, policyId: string, assetName: string) → TxBuilder

Mint or burn assets.

Parameters:

  • quantity (string): Quantity (negative to burn)
  • policyId (string): Policy ID
  • assetName (string): Asset name

Example:

// Mint tokens
txBuilder.mint('1000', policyId, 'TokenName')

// Burn tokens
txBuilder.mint('-500', policyId, 'TokenName')

Returns: TxBuilder


TxBuilder.mintingScript(policy: PolicyScript) → TxBuilder

Attach the minting policy for the assets added with mint(). Required whenever you mint or burn.

Parameters:

  • policy (PolicyScript): { type: 'Native' | 'PlutusV1' | 'PlutusV2' | 'PlutusV3'; scriptCborHex: string }

Example:

txBuilder
  .mint('1000000', policyId, assetNameHex)
  .mintingScript({ type: 'Native', scriptCborHex })

Returns: TxBuilder


TxBuilder.mintRedeemerValue(redeemer: Redeemer) → TxBuilder

Attach a redeemer to a Plutus minting policy (for PlutusV1/V2/V3 mints). Build the redeemer with buildRedeemer / RedeemerUtils.mkMintRedeemer.

Returns: TxBuilder


TxBuilder.txInScript(scriptCbor: string, version?: LanguageVersion) → TxBuilder

Attach a Plutus script to the last input.

Parameters:

  • scriptCbor (string): Script in CBOR hex format
  • version? (LanguageVersion): Plutus language version — 'V1' | 'V2' | 'V3'. Default: 'V3'.

Example:

txBuilder.txIn(txHash, outputIndex)
  .txInScript(scriptCbor)          // defaults to Plutus V3
  .txInScript(scriptCbor, 'V2')    // or pin the version explicitly

Returns: TxBuilder


TxBuilder.txInDatumHash(datum: PlutusData) → TxBuilder

Add a datum hash for script input.

Parameters:

  • datum (PlutusData): Plutus data

Example:

const datum = CardanoWASM.PlutusData.from_hex('d87980')
txBuilder.txIn(txHash, outputIndex)
  .txInDatumHash(datum)

Returns: TxBuilder


TxBuilder.txInInlineDatum(inlineDatum: PlutusData) → TxBuilder

Add an inline datum for script input.

Parameters:

  • inlineDatum (PlutusData): Plutus data

Example:

const inlineDatum = CardanoWASM.PlutusData.from_hex('d87980')
txBuilder.txIn(txHash, outputIndex)
  .txInInlineDatum(inlineDatum)

Returns: TxBuilder


TxBuilder.txInEmptyRedeemer() → TxBuilder

Add an empty redeemer for script input.

Example:

txBuilder.txIn(txHash, outputIndex)
  .txInScript(scriptCbor)
  .txInEmptyRedeemer()

Returns: TxBuilder


TxBuilder.txInRedeemerValue(redeemer: Redeemer) → TxBuilder

Attach a redeemer to the last script input (the spend redeemer). Build it with buildRedeemer (below) or RedeemerUtils.mkRedeemer / mkSpendRedeemer from @hydra-sdk/core. Use this instead of txInEmptyRedeemer() when the script needs real redeemer data.

Parameters:

  • redeemer (Redeemer): The redeemer to attach

Example:

import { buildRedeemer } from '@hydra-sdk/transaction'

const redeemer = buildRedeemer({ action: 'claim' }, { tag: 'SPEND', index: 0 })
txBuilder.txIn(scriptHash, scriptIdx)
  .txInScript(scriptCbor)
  .txInRedeemerValue(redeemer)

Returns: TxBuilder


TxBuilder.txOutInlineDatumValue(inlineDatum: PlutusData) → TxBuilder

Add an inline datum for the last output.

Parameters:

  • inlineDatum (PlutusData): Plutus data

Example:

const datum = CardanoWASM.PlutusData.from_hex('d87980')
txBuilder.txOut('addr_test1...', amount)
  .txOutInlineDatumValue(datum)

Returns: TxBuilder


TxBuilder.registerStake(rewardAddress: string) → TxBuilder

Register a stake address.

Parameters:

  • rewardAddress (string): Stake reward address

Example:

txBuilder.registerStake('stake_test1...')
Since v1.2.0 the staged certificate is actually written to the transaction body (via set_certs). In earlier versions it was silently dropped at build time. An invalid reward address now throws.

Returns: TxBuilder


TxBuilder.deregisterStake(rewardAddress: string) → TxBuilder

Deregister (retire) a stake address, reclaiming its key deposit.

Parameters:

  • rewardAddress (string): Stake reward address

Example:

txBuilder.deregisterStake('stake_test1...')

Returns: TxBuilder


TxBuilder.delegateStake(rewardAddress: string, poolKeyHash: string) → TxBuilder

Delegate stake to a pool.

Parameters:

  • rewardAddress (string): Stake reward address
  • poolKeyHash (string): Pool key hash

Example:

txBuilder.delegateStake('stake_test1...', 'pool1...')
As with registerStake / deregisterStake, the delegation certificate is applied to the transaction body since v1.2.0. PoolRegistration / PoolRetirement are not supported yet and throw a clear error.

Returns: TxBuilder


TxBuilder.withdrawal(rewardAddress: string, amount: string) → TxBuilder

Withdraw rewards from a stake address.

Parameters:

  • rewardAddress (string): Stake reward address
  • amount (string): Amount in lovelace

Example:

txBuilder.withdrawal('stake_test1...', '5000000')

Returns: TxBuilder


TxBuilder.txInCollateral(txHash: string, outputIndex: number, amount: Asset[], address: string) → TxBuilder

Add a collateral input.

Parameters:

  • txHash (string): Transaction hash
  • outputIndex (number): Output index
  • amount (Asset): Asset amounts
  • address (string): Address

Example:

txBuilder.txInCollateral(
  collateralTxHash,
  0,
  [{ unit: 'lovelace', quantity: '5000000' }],
  'addr_test1...'
)

Returns: TxBuilder


TxBuilder.totalCollateral(amount: string) → TxBuilder

Set the total collateral amount.

Parameters:

  • amount (string): Amount in lovelace

Example:

txBuilder.totalCollateral('5000000')
Since v1.2.0 this value is written to the transaction via set_total_collateral. In earlier versions it was stored but never applied to the built transaction.

Returns: TxBuilder


TxBuilder.metadataValue(label: string, metadata: any) → TxBuilder

Add metadata to the transaction.

Parameters:

  • label (string): Metadata label
  • metadata (any): Metadata content

Example:

// NFT metadata (CIP-25)
txBuilder.metadataValue('721', {
  [policyId]: {
    [assetName]: {
      name: 'My NFT',
      image: 'ipfs://...',
      description: 'A unique NFT'
    }
  }
})

// Text metadata
txBuilder.metadataValue('674', {
  msg: ['Hello', 'World']
})

Returns: TxBuilder


TxBuilder.invalidBefore(slot: number) → TxBuilder

Set the transaction validity start time.

Parameters:

  • slot (number): Slot number

Example:

txBuilder.invalidBefore(1000000)

Returns: TxBuilder


TxBuilder.invalidAfter(slot: number) → TxBuilder

Set the transaction validity end time.

Parameters:

  • slot (number): Slot number

Example:

txBuilder.invalidAfter(2000000)

Returns: TxBuilder


TxBuilder.complete() → Promise<Transaction>

Build and complete the transaction.

Example:

const tx = await txBuilder
  .setInputs(utxos)
  .txOut('addr_test1...', [{ unit: 'lovelace', quantity: '2000000' }])
  .changeAddress(changeAddress)
  .complete()

console.log('Transaction CBOR:', tx.to_hex())
complete() returns a live WASM Transaction object. When you only need the serialized bytes, prefer , which frees the object for you. Otherwise call tx.free() once you are done with it — WASM objects are not reclaimed promptly by the JS garbage collector, so failing to free them leaks memory under high volume. See Performance.

If an evaluator was supplied to the constructor and the transaction contains Plutus redeemers, complete() runs a second build pass to write real exUnits into the redeemers before returning (see ).

Returns: Promise<Transaction>
Completed transaction (caller-owned; call .free() or use completeCbor())


TxBuilder.completeCbor() → Promise<string>

Build the transaction and return its CBOR hex, freeing the intermediate Transaction immediately. This is the leak-free path for high-volume workloads where you only need the serialized bytes — prefer it over complete() + .to_hex().

Example:

const cbor = await new TxBuilder()
  .setInputs(utxos)
  .txOut('addr_test1...', [{ unit: 'lovelace', quantity: '2000000' }])
  .changeAddress(changeAddress)
  .completeCbor()

const signedTx = await wallet.signTx(cbor)

Returns: Promise<string>
Transaction CBOR hex


TxBuilder.dispose() → void

Release all WASM memory held by the builder. After dispose() the builder must not be used again. Also exposed as [Symbol.dispose], so a builder created with the using keyword is cleaned up automatically when it leaves scope.

Example:

// Manual disposal
const builder = new TxBuilder()
const cbor = await builder.setInputs(utxos)./* … */.completeCbor()
builder.dispose()

// Automatic disposal (TypeScript 5.2+ / `using`)
{
  using builder = new TxBuilder()
  const cbor = await builder.setInputs(utxos)./* … */.completeCbor()
} // builder.dispose() called here
For spike / high-throughput workloads (thousands of builds), reuse one builder with reset() between transactions and dispose() it at the end, or create a fresh using builder per transaction. Both keep WASM memory flat. See Performance.

Returns: void


TxBuilder.reset() → TxBuilder

Reset the builder to its initial state.

Example:

txBuilder.reset()
// Builder is ready for a new transaction

Returns: TxBuilder


TxBuilder.calculateFee() → BigNum

Calculate the minimum fee.

Note: In the current release this method is a stub — it always returns CardanoWASM.BigNum.zero(). The fee is computed internally during complete(); do not rely on calculateFee() for a real fee estimate yet.

Example:

const minFee = txBuilder.calculateFee()
console.log('Minimum fee:', minFee.to_str()) // currently always '0'

Returns: BigNum
Minimum fee (currently always 0)


TxBuilder.setFee(fee: string | BigNum) → TxBuilder

Force an explicit fee instead of letting complete() compute the minimum. The main use is Hydra, where Layer 2 transactions carry no fee — set '0'. On Layer 1 you normally omit this and let build_tx() compute the fee.

Example:

// Inside a Hydra Head — no fee
txBuilder.setFee('0')

Returns: TxBuilder


TxBuilder.setMinFee(minFee: string | BigNum) → TxBuilder

Set a floor the computed fee will not go below during complete().

Returns: TxBuilder


Other builder methods

TxBuilder exposes further chainable methods that mirror the ones above (see the source for full signatures):

  • Reference inputs / scripts (CIP-31/33): txInReference(txHash, outputIndex), txOutReferenceScript(scriptCbor, version?)
  • Explicit script versions: spendingPlutusScript(version), mintPlutusScript(version)
  • Datums & collateral: txOutDatumHashValue(datum), collateralReturn(address, amount)
  • Signing & metadata: requiredSignerHash(pubKeyHash), auxiliaryData(hash)
  • Protocol params: updateProtocolParams(params)
  • Min-ADA (static): TxBuilder.minAdaForAssets(assets)

TxBuilder.minAda(output: TransactionOutput, protocolParams: Protocol) → BigNum (Static)

Calculate minimum ADA for an output.

Parameters:

  • output (TransactionOutput): Transaction output
  • protocolParams (Protocol): Protocol parameters

Example:

const output = CardanoWASM.TransactionOutput.new(
  CardanoWASM.Address.from_bech32('addr_test1...'),
  CardanoWASM.Value.new(CardanoWASM.BigNum.from_str('1000000'))
)

const minAda = TxBuilder.minAda(output, protocolParams)
console.log('Minimum ADA:', minAda.to_str())

Returns: BigNum
Minimum ADA amount


Utility Functions

Building datums

The @hydra-sdk/transaction package exports only the tx-builder and the redeemer-builder helpers — there is no buildDatum export. When you need to construct PlutusData for a datum, use DatumUtils from @hydra-sdk/core.

Example:

import { DatumUtils } from '@hydra-sdk/core'

// Constr(0, [Int(42), Bytes('a1b2')])
const datum = DatumUtils.mkConstr(0, [
  DatumUtils.mkInt(42),
  DatumUtils.mkBytes('a1b2')
])

Returns: PlutusData


buildRedeemer(jsValue: Record<string, string>, options?) → Redeemer

Build a redeemer for a Plutus script. jsValue is required — each value is encoded as a bytes field of a Constr(0, …) redeemer. Calling it with no argument throws.

Parameters:

  • jsValue (Record<string, string>): Values to encode as redeemer fields
  • options?: { tag?: 'SPEND' | 'MINT' | 'REWARD' | 'CERT' | 'VOTE' | 'VOTING_PROPOSAL'; index?: number | string; exUnits?: { mem: string; steps: string } } (tag default 'SPEND', index default 0)

Example:

import { buildRedeemer } from '@hydra-sdk/transaction'

const redeemer = buildRedeemer({ action: 'claim' }, { tag: 'SPEND', index: 0 })

For most cases prefer emptyRedeemer(...) (below) or RedeemerUtils.mkRedeemer(...) from @hydra-sdk/core, which build a redeemer directly from PlutusData.

Returns: Redeemer


emptyRedeemer() → Redeemer

Create an empty redeemer.

Example:

import { emptyRedeemer } from '@hydra-sdk/transaction'

const redeemer = emptyRedeemer()

Returns: Redeemer


MetadataUtils.metadataObjToMetadatum(obj: any) → TransactionMetadatum

Convert a metadata object to a TransactionMetadatum. This helper lives in @hydra-sdk/core under the MetadataUtils namespace — it is not exported from @hydra-sdk/transaction.

Parameters:

  • obj (any): Metadata object

Example:

import { MetadataUtils } from '@hydra-sdk/core'

const metadata = MetadataUtils.metadataObjToMetadatum({
  '1': 'Hello',
  '2': 12345
})

Returns: TransactionMetadatum


Types and Interfaces

TxBuilderOptions

interface TxBuilderOptions {
  fetcher?: IFetcher
  submitter?: ISubmitter
  /**
   * Optional script evaluator (v1.2.0+). When provided and the transaction has
   * Plutus redeemers, complete() runs a second build pass with the evaluated
   * budgets so the fee is correct. Omit for Hydra (no on-chain evaluation).
   */
  evaluator?: IEvaluator
  /** Safety multiplier applied to evaluated exUnits (e.g. 1.1). Default: 1. (v1.2.0+) */
  txEvaluationMultiplier?: number
  isHydra?: boolean
  params?: Partial<Protocol>
  verbose?: boolean
  /**
   * Strict/testing mode: throw instead of continuing when tx building hits an
   * issue (e.g. coin selection fails). Default: false. Not for production use.
   */
  errorLogger?: boolean
}

IEvaluator, EvalAction, Budget

The evaluator contract for computing real Plutus execution units. The shapes match MeshJS's IEvaluator for interoperability — pass any provider-backed evaluator (Blockfrost / Ogmios / Demeter) or an offline UPLC evaluator.

interface Budget {
  mem: number
  steps: number
}

type EvalRedeemerTag = 'SPEND' | 'MINT' | 'CERT' | 'REWARD' | 'VOTE' | 'PROPOSE'

interface EvalAction {
  tag: EvalRedeemerTag
  index: number
  budget: Budget
}

interface IEvaluator {
  evaluateTx(txHex: string, additionalUtxos?: UTxO[], additionalTxs?: string[]): Promise<EvalAction[]>
}

CoinSelectionStrategy

type CoinSelectionStrategy =
  | 'LargestFirst'
  | 'RandomImprove'
  | 'LargestFirstMultiAsset'
  | 'RandomImproveMultiAsset'

Note: TxBuilderOptions and CoinSelectionStrategy are internal types — they are not re-exported from @hydra-sdk/transaction. The shapes are shown here for reference; pass plain object/string literals to the constructor and to setInputs.

Asset

interface Asset {
  unit: string      // 'lovelace' or 'policyId.assetName'
  quantity: string  // Amount as string
}

Script evaluation (exUnits)

CSL cannot evaluate Plutus scripts itself, so by default TxBuilder keeps the placeholder exUnits on script redeemers. Supply an evaluator to compute the real execution units and get an accurately-priced transaction.

When an evaluator is set and the transaction has redeemers, complete():

  1. Builds a draft transaction.
  2. Calls evaluator.evaluateTx(draftCbor) to obtain real budgets per redeemer.
  3. Writes them back — SPEND matched by input txHash#index, MINT by policy id — optionally scaled by txEvaluationMultiplier.
  4. Rebuilds so the fee reflects the true script cost.
import { TxBuilder, IEvaluator } from '@hydra-sdk/transaction'

// A provider-backed evaluator (Blockfrost / Ogmios / Demeter) or a UPLC evaluator
const evaluator: IEvaluator = myProvider.evaluator

const tx = await new TxBuilder({ evaluator, txEvaluationMultiplier: 1.1 })
  .setInputs(utxos)
  .txIn(scriptUtxoHash, scriptUtxoIndex)
  .txInScript(scriptCbor)
  .txInEmptyRedeemer()
  .txInCollateral(collateralHash, 0, [{ unit: 'lovelace', quantity: '5000000' }], collateralAddress)
  .txOut(recipientAddress, [{ unit: 'lovelace', quantity: '2000000' }])
  .changeAddress(changeAddress)
  .complete() // exUnits are evaluated and written back automatically
Current limits: only SPEND and MINT budgets are remapped (CERT / REWARD / VOTE / PROPOSE are returned by the evaluator but not yet applied), and there is no bundled offline evaluator — supply your own. The rebuild runs a single extra pass, so a txEvaluationMultiplier of ~1.1 is recommended when exact fees matter. Without an evaluator (e.g. inside a Hydra Head), behaviour is unchanged.

Complete Examples

Simple ADA Transfer

import { TxBuilder } from '@hydra-sdk/transaction'
import { AppWallet } from '@hydra-sdk/core'

async function sendAda() {
  const wallet = new AppWallet({
    networkId: 0,
    key: { type: 'mnemonic', words: AppWallet.brew() }
  })

  const account = wallet.getAccount(0, 0)
  const utxos = await wallet.queryUtxos(account.baseAddressBech32)

  const tx = await new TxBuilder()
    .setInputs(utxos)
    .txOut('addr_test1qqr585tvlc7ylnqvz8pyqwauzrdu0mxag3m7q56grgmgu7sxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flknswgndm3', [
      { unit: 'lovelace', quantity: '2000000' }
    ])
    .changeAddress(account.baseAddressBech32)
    .complete()

  const signedTx = await wallet.signTx(tx.to_hex(), false, 0, 0)
  console.log('Signed transaction:', signedTx)
}

Multi-Asset Transaction

async function sendMultiAsset() {
  const tx = await new TxBuilder()
    .setInputs(utxos)
    .txOut('addr_test1...', [
      { unit: 'lovelace', quantity: '2000000' },
      { unit: 'policyId.TokenA', quantity: '100' },
      { unit: 'policyId.TokenB', quantity: '50' }
    ])
    .changeAddress(changeAddress)
    .complete()

  return tx
}

NFT Minting with Metadata

async function mintNFT() {
  const policyId = 'your_policy_id'
  const assetName = 'MyNFT'
  const mintingScript = 'minting_script_cbor'

  const tx = await new TxBuilder()
    .setInputs(utxos)
    .mint('1', policyId, assetName)
    .txOut(recipientAddress, [
      { unit: 'lovelace', quantity: '2000000' },
      { unit: `${policyId}.${assetName}`, quantity: '1' }
    ])
    .metadataValue('721', {
      [policyId]: {
        [assetName]: {
          name: 'My Unique NFT',
          image: 'ipfs://QmHash...',
          description: 'A one-of-a-kind digital collectible'
        }
      }
    })
    .changeAddress(changeAddress)
    .complete()

  return tx
}

Plutus Script Interaction

async function interactWithPlutusScript() {
  const scriptCbor = 'plutus_script_cbor'
  const datum = CardanoWASM.PlutusData.new_map(
    CardanoWASM.PlutusMap.new()
  )

  const tx = await new TxBuilder()
    .setInputs(utxos)
    .txIn(scriptUtxoHash, scriptUtxoIndex)
    .txInScript(scriptCbor)
    .txInDatumHash(datum)
    .txInEmptyRedeemer()
    .txInCollateral(collateralHash, 0, [{ unit: 'lovelace', quantity: '5000000' }], collateralAddress)
    .txOut(recipientAddress, [{ unit: 'lovelace', quantity: '10000000' }])
    .changeAddress(changeAddress)
    .complete()

  return tx
}