Transaction Building Examples

Learn how to build various types of transactions using real patterns from the Hydra SDK.

1. Simple ADA Transfer (Based on nodejs-playground/examples)

Using Blockfrost Provider

typescript
import { AppWallet, NETWORK_ID, ProviderUtils } from '@hydra-sdk/core'
import { TxBuilder } from '@hydra-sdk/transaction'

// Initialize Blockfrost provider
const blockfrostProvider = new ProviderUtils.BlockfrostProvider({
  apiKey: 'your-blockfrost-api-key',
  network: 'preprod'
})

// Create wallet (từ mẫu thực tế)
const wallet = new AppWallet({
  key: {
    type: 'mnemonic',
    words: 'armed pink solve client dignity alarm earn impose acquire rib eyebrow engage dragon face funny'.split(' ')
  },
  networkId: NETWORK_ID.PREPROD
})

async function buildSimpleTransaction() {
  const baseAddressBech32 = wallet.getAccount().baseAddressBech32
  console.log('>>> Wallet Address:', baseAddressBech32)

  // Fetch UTxOs từ Blockfrost
  const utxos = await blockfrostProvider.fetcher.fetchAddressUTxOs(baseAddressBech32)
  console.log('>>> UTxOs found:', utxos.length)

  // Build transaction
  const txBuilder = await new TxBuilder({})

  const tx = await txBuilder
    .setInputs(utxos)
    .addOutput({
      address: baseAddressBech32, // Send back to same address
      amount: [{ unit: 'lovelace', quantity: '1000000' }] // 1 ADA
    })
    .setChangeAddress(baseAddressBech32)
    .complete()

  console.log('>>> Unsigned Tx:', tx.to_hex())
  
  // Sign transaction
  const signedTx = await wallet.signTx(tx.to_hex())
  console.log('>>> Signed Tx:', signedTx)

  // Submit transaction (optional)
  // const txHash = await blockfrostProvider.submitter.submitTx(signedTx)
  // console.log('>>> Tx Hash:', txHash)
}

Using Ogmios Provider

typescript
import { AppWallet, NETWORK_ID, ProviderUtils } from '@hydra-sdk/core'
import { TxBuilder } from '@hydra-sdk/transaction'

// Initialize Ogmios provider
const ogmiosProvider = new ProviderUtils.OgmiosProvider({
  apiEndpoint: 'https://preprod.cardano-rpc.hydrawallet.app',
  network: 'preprod'
})

const wallet = new AppWallet({
  key: {
    type: 'mnemonic', 
    words: 'armed pink solve client dignity alarm earn impose acquire rib eyebrow engage dragon face funny'.split(' ')
  },
  networkId: NETWORK_ID.PREPROD
})

async function buildTransactionWithOgmios() {
  const baseAddressBech32 = wallet.getAccount().baseAddressBech32
  console.log('>>> Wallet Address:', baseAddressBech32)

  // Fetch UTxOs từ Ogmios
  const utxos = await ogmiosProvider.fetcher.fetchAddressUTxOs(baseAddressBech32)
  console.log('>>> UTxOs found:', JSON.stringify(utxos, null, 1))

  const txBuilder = await new TxBuilder({})

  const tx = await txBuilder
    .setInputs(utxos)
    .addOutput({
      address: baseAddressBech32,
      amount: [{ unit: 'lovelace', quantity: '1000000' }]
    })
    .setChangeAddress(baseAddressBech32)
    .complete()

  console.log('>>> Unsigned Tx:', tx.to_hex())
  const signedTx = await wallet.signTx(tx.to_hex())
  console.log('>>> Signed Tx:', signedTx)

  // Submit với Ogmios
  const txHash = await ogmiosProvider.submitter.submitTx(signedTx)
  console.log('>>> Submitted Tx Hash:', txHash)
}

2. Token Minting (Based on mint-burn-token.ts)

Mint Native Tokens với Metadata

typescript
import {
  AppWallet,
  DatumUtils,
  deserializeTx,
  NETWORK_ID,
  PolicyUtils,
  serializeAssetUnit,
  ParserUtils
} from '@hydra-sdk/core'
import { TxBuilder } from '@hydra-sdk/transaction'

// Wallet setup (từ playground example)
const wallet = new AppWallet({
  key: {
    type: 'mnemonic',
    words: 'enable away depend exist mad february table onion census praise spawn pipe again angle grant'.split(' ')
  },
  networkId: NETWORK_ID.PREPROD
})

async function mintToken() {
  const walletAddress = wallet.getAccount().baseAddressBech32
  console.log('>>> Wallet Address:', walletAddress)

  // Query UTxOs (sử dụng Hexcore API như trong example)
  const utxos = await queryAddressUTxO(walletAddress)
  console.log('>>> UTxOs found:', utxos.length)

  // Tìm collateral UTxO (cần cho minting)
  const collateralUTxOs = utxos.filter(u =>
    u.output.amount.find(a => a.unit === 'lovelace' && Number(a.quantity) >= 5_000_000)
  )
  
  if (!collateralUTxOs.length) {
    throw new Error('No collateral UTxOs found')
  }
  const collateralUTxO = collateralUTxOs[0]
  
  // Tạo minting policy từ address
  const scriptCborHex = PolicyUtils.buildMintingPolicyScriptFromAddress(walletAddress)
  const policyId = PolicyUtils.policyIdFromNativeScript(scriptCborHex)
  const assetNameHex = ParserUtils.stringToHex('AniaToken')
  
  // Metadata cho token (từ playground example)
  const assetMetadata = {
    name: 'Ada Binary Option Token',
    description: 'Utility token for Cardano Binary Option demo project',
    ticker: 'tABO',
    url: 'https://preprod.ada-defi.io.vn',
    logo: 'ipfs://Qmaqj4Lg51s9gL654zwFfcimHNcX4GLno7okEdyCGPor2i',
    image: 'ipfs://Qmaqj4Lg51s9gL654zwFfcimHNcX4GLno7okEdyCGPor2i'
  }

  console.log('>>> Policy ID:', policyId)
  console.log('>>> Script:', scriptCborHex)

  // Build minting transaction
  const txBuilder = new TxBuilder()

  const tx = await txBuilder
    .setInputs(
      utxos.filter(
        u => `${u.input.txHash}#${u.input.outputIndex}` !== 
             `${collateralUTxO.input.txHash}#${collateralUTxO.input.outputIndex}`
      )
    )
    .mint('1000000', policyId, assetNameHex) // Mint 1 million tokens
    .mintingScript({
      type: 'Native',
      scriptCborHex: scriptCborHex
    })
    .metadataValue(721, { 
      [policyId]: { 
        [assetNameHex]: { ...assetMetadata } 
      } 
    })
    .txInCollateral(
      collateralUTxO.input.txHash,
      collateralUTxO.input.outputIndex,
      collateralUTxO.output.amount,
      collateralUTxO.output.address
    )
    .addOutput({
      address: walletAddress,
      amount: [
        { unit: 'lovelace', quantity: String(2_000_000) },
        { unit: serializeAssetUnit(policyId, assetNameHex), quantity: '1000000' }
      ]
    })
    .changeAddress(walletAddress)
    .complete()

  console.log('>>> Unsigned tx:', tx.to_hex())
  const signedCbor = await wallet.signTx(tx.to_hex())
  console.log('>>> Signed tx:', signedCbor)
  console.log('>>> Tx ID:', deserializeTx(signedCbor).transaction_hash().to_hex())
}

3. Token Burning

typescript
async function burnToken() {
  const walletAddress = wallet.getAccount().baseAddressBech32
  const utxos = await queryAddressUTxO(walletAddress)
  
  // Setup collateral (same as minting)
  const collateralUTxOs = utxos.filter(u =>
    u.output.amount.find(a => a.unit === 'lovelace' && Number(a.quantity) >= 5_000_000)
  )
  const collateralUTxO = collateralUTxOs[0]

  // Policy setup
  const scriptCborHex = PolicyUtils.buildMintingPolicyScriptFromAddress(walletAddress)
  const policyId = PolicyUtils.policyIdFromNativeScript(scriptCborHex)
  const assetNameHex = ParserUtils.stringToHex('AniaToken')

  // Build burn transaction (negative amount)
  const txBuilder = new TxBuilder()

  const tx = await txBuilder
    .setInputs(
      utxos.filter(
        u => `${u.input.txHash}#${u.input.outputIndex}` !== 
             `${collateralUTxO.input.txHash}#${collateralUTxO.input.outputIndex}`
      )
    )
    .mint('-1000000', policyId, assetNameHex) // Negative = burn
    .mintingScript({
      type: 'Native',
      scriptCborHex: scriptCborHex
    })
    .changeAddress(walletAddress)
    .complete()

  console.log('>>> Burn tx:', tx.to_hex())
  const signedCbor = await wallet.signTx(tx.to_hex())
  console.log('>>> Signed burn tx:', signedCbor)
}

4. Smart Contract Interactions

4.1 Lock Assets in Contract Script

This example demonstrates how to lock an asset in a Plutus script (contract). The contract address is provided, and we will send 2 ADA to the contract.

typescript
import { AppWallet, deserializeTx, NETWORK_ID, UTxO } from '@hydra-sdk/core'
import { TxBuilder } from '@hydra-sdk/transaction'

const lock = async () => {
  try {
    // =========== Prepare Wallet ===========
    const blockfrostProvider = new ProviderUtils.BlockfrostProvider({
      apiKey: 'your-blockfrost-api-key',
      network: 'preprod'
    })
    const wallet = new AppWallet({
      key: {
        type: 'mnemonic',
        words: 'your_mnemonic_words'.split(' ')
      },
      networkId: NETWORK_ID.PREPROD,
      fetcher: blockfrostProvider.fetcher,
      submitter: blockfrostProvider.submitter
    })
    const account = wallet.getAccount(0, 0)

    /**
     * Query UTxOs for the account using Browser Wallet (Lace, Nami, etc.)
     * or Blockfrost API.
     * In this example, we're using Hexcore API.
     */
    const addressUTxOs: UTxO[] = await wallet.queryUTxOs(account.baseAddressBech32)

    // =========== Contract Configuration ===========
    const contract = {
      // Always true contract
      address: 'addr_test1wr47xyv2z8vjgxw3kckdelrl9lunw59hegma8t7jtzu0k8qw08cvd',
      type: 'PlutusScriptV3',
      description: 'Generated by Aiken'
    }

    // =========== Build Lock Transaction ===========
    const datum = DatumUtils.mkConstr(0, [
      DatumUtils.mkInt(42),
      DatumUtils.mkString("Hello, World!")
    ])
    const txBuilder = new TxBuilder()
    const txLock = await txBuilder
      .setInputs(addressUTxOs) // Select UTxOs for inputs
      .txOut(contract.address, [{ unit: 'lovelace', quantity: '2000000' }]) // Send 2 ADA to contract
      .txOutInlineDatumValue(datum)
      .changeAddress(account.baseAddressBech32)
      .complete()

    // =========== Sign Transaction ===========
    const unsignedCborHex = txLock.to_hex()
    const signedCbor = await wallet.signTx(unsignedCborHex)

    // Get transaction hash (optional)
    const txHash = deserializeTx(signedCbor).transaction_hash()

    // =========== Submit Transaction ===========
    /**
     * Submit the signed transaction to the Cardano network.
     * You can use:
     * - Hexcore API
     * - Ogmios API
     * - Blockfrost API (https://docs.blockfrost.io)
     * - Wallet Extension API (HydraWallet, Eternl, etc.)
     */
    const txId = await wallet.submitTx(signedCbor)

    console.log('Transaction submitted successfully:', txId)
    return txId

  } catch (error) {
    console.error('Error creating lock transaction:', error)
    throw error
  }
}

// Execute the lock function
lock()

4.2 Unlock Assets from Contract Script

This example demonstrates how to unlock an asset that was previously locked in a Plutus script (contract). It assumes that you have already locked an asset in the contract using the above example.

The contract address and script UTxO information are required. This contract is a Plutus script that requires a specific redeemer to unlock the asset, and no datum is needed.

typescript
import { AppWallet, deserializeTx, NETWORK_ID, UTxO } from '@hydra-sdk/core'
import { buildRedeemer, TxBuilder } from '@hydra-sdk/transaction'

const unlock = async (txHash: `${string}#${number}`) => {
  try {
    // =========== Prepare Wallet ===========
    const blockfrostProvider = new ProviderUtils.BlockfrostProvider({
      apiKey: 'your-blockfrost-api-key',
      network: 'preprod'
    })
    const wallet = new AppWallet({
      key: {
        type: 'mnemonic',
        words: 'your_mnemonic_words'.split(' ')
      },
      networkId: NETWORK_ID.PREPROD,
      fetcher: blockfrostProvider.fetcher,
      submitter: blockfrostProvider.submitter
    })
    const account = wallet.getAccount(0, 0)

    /**
     * Query UTxOs for the account using Browser Wallet (Lace, Nami, etc.)
     * or Blockfrost API.
     * In this example, we're using Hexcore API.
     */
    const addressUTxOs: UTxO[] = await wallet.queryUTxOs(account.baseAddressBech32)

    // =========== Prepare Collateral UTxO ===========
    // Find collateral UTxO: needs at least 5 ADA in lovelace
    const collateralUTxOs = addressUTxOs.filter(utxo =>
      utxo.output.amount.find(asset => 
        asset.unit === 'lovelace' && Number(asset.quantity) >= 5_000_000
      )
    )
    
    if (!collateralUTxOs.length) {
      throw new Error('No suitable collateral UTxOs found (minimum 5 ADA required)')
    }
    
    const collateralUTxO = collateralUTxOs[0]

    // =========== Contract Configuration ===========
    const contract = {
      // Always true contract
      address: 'addr_test1wr47xyv2z8vjgxw3kckdelrl9lunw59hegma8t7jtzu0k8qw08cvd',
      type: 'PlutusScriptV3',
      description: 'Generated by Aiken',
      cborHex: '58c958c701010029800aba2aba1aab9faab9eaab9dab9a48888896600264653001300700198039804000cc01c0092225980099b8748008c01cdd500144c8cc896600266e1d2000300a375400d132323322598009809001c56600266e3cdd7180898079baa00a48810a7365637265745f6b6579008cc004cdc79bae30113012300f375401491100a50a51403514a0806a2c8080dd718078009bae300f002300f001300b375400d16402460160026016601800260106ea800a2c8030600e00260066ea801e29344d95900101'
    }

    // =========== Query Contract UTxO ===========
    /**
     * Find the UTxO that corresponds to the script we want to unlock.
     * Ensure that the asset is actually locked in the script.
     */
    const contractUTxOs = await wallet.queryUTxOs(contract.address)
    const scriptUTxO = contractUTxOs.find(utxo => 
      `${utxo.input.txHash}#${utxo.input.outputIndex}` === txHash
    )
    
    if (!scriptUTxO) {
      throw new Error(`No script UTxO found for transaction: ${txHash}`)
    }

    // =========== Build Redeemer ===========
    /**
     * Redeemer data is required to unlock the asset.
     * The structure depends on the specific contract requirements.
     * In this case, we need to provide:
     * - secret_key: A string value
     * - receive_addr: The address to receive the unlocked assets
     */
    const txRedeemer = buildRedeemer({
      key: 'secret_key',
      receive_addr: 'addr_test1qpxsf0x8xypuhq5k408f9kh0meyy6jv2lxgqw2fefvjlte0u06dugtmxuhhw8hschdn4q59g64q5s9z42ax6qyg7ewsqt6e548'
    })

    // =========== Build Unlock Transaction ===========
    const txBuilder = new TxBuilder()
    const txUnlock = await txBuilder
      // Add additional UTxO for transaction fees
      .setInputs([collateralUTxOs[1]]) 
      // Add the script UTxO as input
      .txIn(
        scriptUTxO.input.txHash, 
        scriptUTxO.input.outputIndex, 
        scriptUTxO.output.amount, 
        scriptUTxO.output.address
      )
      // Attach redeemer to the script input
      .txInRedeemerValue(txRedeemer)
      // Attach the Plutus script
      .txInScript(contract.cborHex)
      // Set collateral for script execution
      .txInCollateral(
        collateralUTxO.input.txHash,
        collateralUTxO.input.outputIndex,
        collateralUTxO.output.amount,
        collateralUTxO.output.address
      )
      // Send unlocked assets back to our address
      .txOut(account.baseAddressBech32, scriptUTxO.output.amount)
      // Set change address
      .changeAddress(account.baseAddressBech32)
      // Set transaction fee (2 ADA)
      .setFee('2000000')

    // =========== Complete and Sign Transaction ===========
    const tx = await txUnlock.complete()
    const unsignedCborHex = tx.to_hex()
    const signedCbor = await wallet.signTx(unsignedCborHex)
    
    // Get transaction hash (optional)
    const unlockTxHash = deserializeTx(signedCbor).transaction_hash()

    // =========== Submit Transaction ===========
    /**
     * Submit the signed transaction to the Cardano network.
     * You can use:
     * - Hexcore API
     * - Ogmios API
     * - Blockfrost API (https://docs.blockfrost.io)
     * - Wallet Extension API (HydraWallet, Eternl, etc.)
     */
    const txHash = await wallet.submitTx(signedCbor)

    console.log('Unlock transaction submitted successfully:', txHash)
    return txHash

  } catch (error) {
    console.error('Error creating unlock transaction:', error)
    throw error
  }
}

// Execute the unlock function with the transaction hash from the lock operation
// unlock('your_lock_transaction_hash#output_index')

5. Key Points

Lock Transaction

  • Uses selectUtxosFrom() for automatic UTxO selection
  • Sends assets to the contract address
  • No special script handling required for locking

Unlock Transaction

  • Requires collateral UTxO (minimum 5 ADA)
  • Uses script UTxO as input with .txIn()
  • Attaches redeemer with .txInRedeemerValue()
  • Attaches Plutus script with .txInScript()
  • Sets collateral with .txInCollateral()
  • Sends unlocked assets to desired address

Error Handling

  • Validates collateral availability
  • Checks script UTxO existence
  • Proper error messages for debugging
  • Transaction hash validation