# Introduction Hydra SDK is a TypeScript toolkit for building Cardano wallet applications with Hydra Layer 2 integration. It wraps the `cardano-serialization-lib` WASM bindings and the Hydra Head protocol behind a modular, type-safe API — delivering native-level performance in the browser while staying compatible with modern build tools like Vite and Rollup. ## Why Hydra SDK? - **WASM-powered** — the core runs on `cardano-serialization-lib` compiled to WebAssembly, up to 10× faster than JavaScript-based SDKs. - **Zero bundle issues** — no polyfills for `util`, `stream`, or `crypto`. Works out of the box with Vite, Rollup, Nuxt 3, React + Vite, and Next.js. - **Browser-first** — designed for DApps, with an environment-aware WASM loader that also runs on Node.js. - **Modular** — install only the packages you need; each ships ESM + CJS. - **Type-safe** — comprehensive TypeScript definitions across every namespace and builder. ::note Looking for the latest release notes? See the [Changelog](https://hydrasdk.com/resources/changelog). :: ## Prerequisites Before you begin, make sure you have: - **Node.js 18.20.0 or higher** - A package manager — **npm**, **pnpm**, or **yarn** - Basic **TypeScript / JavaScript** knowledge - Familiarity with core **Cardano** concepts (addresses, UTxOs, transactions) ## Architecture The SDK is a monorepo of four packages that build on each other: | Package | Responsibility | | ------------------------------------------------------------------ | -------------------------------------------------------------- | | [`@hydra-sdk/core`](https://hydrasdk.com/api/core) | HD wallet creation & management, UTxO types, Cardano utilities | | [`@hydra-sdk/bridge`](https://hydrasdk.com/api/bridge) | Hydra Head lifecycle over WebSocket + REST | | [`@hydra-sdk/transaction`](https://hydrasdk.com/api/transaction) | Low-level TxBuilder for Layer 1 and Hydra | | [`@hydra-sdk/cardano-wasm`](https://hydrasdk.com/api/cardano-wasm) | Environment-aware WASM bindings | ```mermaid graph LR A[Your App] --> B[core] A --> C[bridge] A --> D[transaction] B --> E[cardano-wasm] C --> B C --> E D --> B D --> E ``` All packages import Cardano primitives through `@hydra-sdk/cardano-wasm` — never import `@emurgo/cardano-serialization-lib` directly. ## Development workflow A typical flow when building with the SDK: ::steps{level="3"} ### Install packages Add the SDK packages your app needs. ### Create a wallet Initialize an `AppWallet` (or `EmbeddedWallet` / `CardanoCLIWallet`) instance. ### Connect to a network Connect to a Cardano network and/or a Hydra Node. ### Build & sign transactions Compose transactions with the `TxBuilder` and sign them with the wallet. ### Submit Submit to the Cardano network or into an open Hydra Head. :: ## Next steps ::card-group :::card --- icon: i-lucide-download title: Installation to: https://hydrasdk.com/getting-started/installation --- Install the SDK packages and configure your bundler. ::: :::card --- icon: i-lucide-rocket title: Quick Start to: https://hydrasdk.com/getting-started/quick-start --- Create your first wallet and run a transaction. ::: :::card --- icon: i-lucide-settings-2 title: Configuration to: https://hydrasdk.com/getting-started/configuration --- Tune the SDK for your framework and environment. ::: :::card --- icon: i-lucide-book-open title: Guides to: https://hydrasdk.com/guides --- Build a wallet app, mint tokens, and work with utilities. ::: :: Need help? Report issues on [GitHub](https://github.com/Vtechcom/hydra-sdk/issues){rel=""nofollow""} or start a [discussion](https://github.com/Vtechcom/hydra-sdk/discussions){rel=""nofollow""}. # Installation ::warning **`@hydra-sdk/bridge` v2 is on the `next` tag.** v2 targets `hydra-node` **2.x** and is a breaking change; the `latest` tag still resolves to **1.3.2**, which speaks `hydra-node` **1.x**. Running a v2 node? Install explicitly: ```bash pnpm add @hydra-sdk/bridge@next ``` See the [migration guide](https://hydrasdk.com/resources/migration) before upgrading. :: ::tip **Using an AI assistant?** Point it at [`INSTALL.md`](https://github.com/Vtechcom/hydra-sdk/blob/master/INSTALL.md){rel=""nofollow""} in the repo for a rule-based setup guide, or connect the docs [MCP server](https://hydrasdk.com/ai) so it can query these pages directly. :: The Hydra SDK is published as individual packages on NPM, allowing you to install only what you need for your project. ## Install with an AI assistant Paste this prompt into your AI coding assistant (Claude, Cursor, Copilot…) to install and configure Hydra SDK. It points the assistant at the machine-readable docs and spells out the rules that commonly trip people up: ```text Install and configure Hydra SDK (@hydra-sdk) in this project. Read these for the exact steps: - https://hydrasdk.com/raw/getting-started/installation.md - https://hydrasdk.com/raw/getting-started/configuration.md Rules (follow strictly): 1. Always install @hydra-sdk/cardano-wasm together with the other @hydra-sdk/* packages. 2. Never import @emurgo/cardano-serialization-lib directly — use @hydra-sdk/cardano-wasm. 3. NETWORK_ID is a Record with keys MAINNET | PREPROD | PREVIEW (no TESTNET), not an enum. 4. For a browser build with Vite, add vite-plugin-wasm + vite-plugin-top-level-await + a buffer polyfill, and exclude @hydra-sdk/cardano-wasm from optimizeDeps. Then create an AppWallet on PREPROD and log its base address to verify the setup. ``` ## System Requirements Before installing the SDK, ensure your system meets these requirements: - **Node.js**: >= 18.20.0 - **npm**: >= 8.0.0 (or **pnpm** >= 8.0.0, **yarn** >= 1.22.0) - **TypeScript**: >= 5.0.0 (optional but recommended) ## Core Packages ### Install Individual Packages Install the packages you need — but always include `@hydra-sdk/cardano-wasm`: ```bash # Core wallet functionality npm install @hydra-sdk/core @hydra-sdk/cardano-wasm # Hydra Layer 2 integration npm install @hydra-sdk/bridge @hydra-sdk/cardano-wasm # Transaction building utilities npm install @hydra-sdk/transaction @hydra-sdk/cardano-wasm ``` ::note `@hydra-sdk/cardano-wasm` is the WASM core that every other package imports through. Even though it is pulled in transitively, install it as a **direct dependency** so your bundler can configure it (`vite-plugin-wasm`, excluding it from `optimizeDeps`) and so pnpm's strict `node_modules` resolves it. See [Configuration](https://hydrasdk.com/getting-started/configuration) for bundler setup. :: ### Install Complete Bundle For most projects, install the core packages together: ```bash # Install all essential packages npm install @hydra-sdk/core @hydra-sdk/bridge @hydra-sdk/transaction @hydra-sdk/cardano-wasm ``` ## Next Steps Now that you have the SDK installed, continue with: - [Quick Start Guide](https://hydrasdk.com/getting-started/quick-start) - Create your first wallet - [Configuration Guide](https://hydrasdk.com/getting-started/configuration) - Set up your build tools - **[Build a Wallet App](https://hydrasdk.com/guides/build-wallet-app)** - Complete working React & Vue apps - [API Reference](https://hydrasdk.com/api) - Explore the full API # Quick Start This guide will walk you through creating your first Cardano wallet application using Hydra SDK. In just a few minutes, you'll have a working wallet that can create accounts, build transactions, and connect to Hydra Layer 2. ## Step 1: Installation Install the essential packages: ```bash npm install @hydra-sdk/core @hydra-sdk/bridge @hydra-sdk/transaction @hydra-sdk/cardano-wasm ``` ## Step 2: Create Your First Wallet Create a new wallet instance with the core package: ```typescript import { AppWallet, NETWORK_ID } from '@hydra-sdk/core' // Generate a new mnemonic phrase const mnemonic = AppWallet.brew() console.log('Generated mnemonic:', mnemonic.join(' ')) // Create wallet instance const wallet = new AppWallet({ networkId: NETWORK_ID.PREPROD, // Use PREPROD for testing key: { type: 'mnemonic', words: mnemonic } }) // Get the first account const account = wallet.getAccount(0, 0) console.log('Wallet Address:', account.baseAddressBech32) ``` ### Restore Existing Wallet To restore a wallet from an existing mnemonic: ```typescript const existingMnemonic = 'your existing mnemonic phrase here'.split(' ') const restoredWallet = new AppWallet({ networkId: NETWORK_ID.PREPROD, key: { type: 'mnemonic', words: existingMnemonic } }) ``` ## Step 3: Query UTxOs and Build Transactions First, query UTxOs from your wallet address, then use the transaction builder: ```typescript import { TxBuilder } from '@hydra-sdk/transaction' import { ProviderUtils } from '@hydra-sdk/core' // Initialize a provider (Blockfrost or Ogmios) const blockfrostProvider = new ProviderUtils.BlockfrostProvider({ apiKey: 'your-blockfrost-api-key', network: 'preprod' }) // Set provider for wallet const wallet = new AppWallet({ networkId: NETWORK_ID.PREPROD, key: { type: 'mnemonic', words: mnemonic }, fetcher: blockfrostProvider.fetcher, submitter: blockfrostProvider.submitter }) // Get wallet address const account = wallet.getAccount(0, 0) const walletAddress = account.baseAddressBech32 // Query UTxOs from wallet const utxos = await wallet.queryUTxOs(walletAddress) console.log('Available UTxOs:', utxos.length) // Build a simple ADA transfer const recipientAddress = 'addr_test1...' // Recipient's address const amountToSend = '1000000' // 1 ADA in lovelace try { // Initialize transaction builder const txBuilder = new TxBuilder() const tx = await txBuilder .setInputs(utxos) // Use queried UTxOs as inputs .addOutput({ address: recipientAddress, amount: [{ unit: 'lovelace', quantity: amountToSend }] }) .changeAddress(walletAddress) // Change returns to sender .complete() console.log('Transaction built:', tx.to_hex()) // Sign the transaction const signedTx = await wallet.signTx(tx.to_hex()) console.log('Transaction signed') // Submit transaction const txHash = await wallet.submitTx(signedTx) console.log('Transaction submitted! Hash:', txHash) } catch (error) { console.error('Transaction failed:', error) } ``` ### Advanced Transaction Example Build a transaction with multiple outputs and native tokens: ```typescript import { TxBuilder } from '@hydra-sdk/transaction' const txBuilder = new TxBuilder() // Query UTxOs const utxos = await wallet.queryUTxOs(walletAddress) const tx = await txBuilder .setInputs(utxos) // Send ADA to first recipient .addOutput({ address: 'addr_test1qr...', amount: [{ unit: 'lovelace', quantity: '2000000' }] }) // Send ADA + Native Token to second recipient .addOutput({ address: 'addr_test1qs...', amount: [ { unit: 'lovelace', quantity: '1500000' }, { unit: 'policyId.assetName', quantity: '100' } ] }) .changeAddress(walletAddress) .complete() const signedTx = await wallet.signTx(tx.to_hex()) const txHash = await wallet.submitTx(signedTx) console.log('Transaction hash:', txHash) ``` ## Step 4: Connect to Hydra Layer 2 Create a Hydra Bridge to connect to Hydra Heads for fast Layer 2 transactions: ```typescript import { HydraBridge, HydraPayload } from '@hydra-sdk/bridge' // Create Hydra Bridge instance const bridge = new HydraBridge({ url: 'ws://localhost:4001', // Your Hydra Node WebSocket URL autoReconnect: true, // Auto-reconnect on drop reconnectInterval: 3000 // Retry every 3s }) // Listen for connection events bridge.events.on('onConnected', () => { console.log('Connected to Hydra Head!') }) bridge.events.on('onMessage', (payload: HydraPayload) => { if (payload.tag === 'HeadIsOpen') { // After Greetings, snapshot cache is seeded automatically // Use getAddressBalance() for O(1) reads — no HTTP call const balance = bridge.getAddressBalance('addr_test1...') console.log('ADA:', balance?.get('lovelace')?.toString()) } }) // Connect to the Hydra Node await bridge.connect() ``` ## Connect to Hydra Layer 2 using HexcoreConnector Create a Hydra Bridge to connect to Hydra Heads using HexcoreConnector: ```typescript import { HydraBridge, HexcoreConnector } from '@hydra-sdk/bridge' const hexcoreConnector = new HexcoreConnector('wss://example.hexcore.io.vn/hydra', { socketIoOptions: { auth: { token: 'your_auth_token' } } }) // Create Hydra Bridge instance const bridge = new HydraBridge({ connector: hexcoreConnector }) // Listen for connection events bridge.events.on('onConnected', () => { console.log('Connected to Hydra Head!') }) bridge.events.on('onMessage', (payload: HydraPayload) => { console.log('>>> / onMessage:', payload) }) // Connect to the Hydra Node await bridge.connect() ``` ## Next Steps Now that you have a working wallet application, explore these advanced topics: - **[Configuration](https://hydrasdk.com/getting-started/configuration)** - Environment setup and network configuration - **[API Reference](https://hydrasdk.com/api/core)** - Complete API documentation - **[Transaction Examples](https://hydrasdk.com/guides/transactions)** - Advanced transaction patterns - **[Hydra Integration](https://hydrasdk.com/guides/hydra-integration)** - Deep dive into Hydra Layer 2 - **[Full React App](https://hydrasdk.com/guides/build-wallet-app)** - Complete React application example - **[Full Vue.js App](https://hydrasdk.com/guides/build-wallet-app)** - Complete Vue.js application example ## Need Help? - Check the [Examples](https://hydrasdk.com/guides) section for more code samples - Visit our [GitHub Repository](https://github.com/Vtechcom/hydra-sdk){rel=""nofollow""} for issues and discussions - **[Examples Repository](https://github.com/Vtechcom/hydra-sdk-examples){rel=""nofollow""}** - Complete working applications # Configuration Hydra SDK leverages WebAssembly (WASM) for high-performance Cardano operations. This guide covers how to configure your build tools and environment for optimal performance. ## Full Examples For complete working examples with full configuration, check out our examples repository: - **[React + Vite Example](https://hydrasdk.com/guides/build-wallet-app)** - Complete React application setup - **[Vue.js + TypeScript Example](https://hydrasdk.com/guides/build-wallet-app)** - Complete Vue.js application setup - **[Examples Repository](https://github.com/Vtechcom/hydra-sdk-examples){rel=""nofollow""}** - All examples on GitHub ## Nuxt 3 For Nuxt.js projects, configure your `nuxt.config.ts`: ```typescript import wasm from 'vite-plugin-wasm' import topLevelAwait from 'vite-plugin-top-level-await' import { nodePolyfills } from 'vite-plugin-node-polyfills' export default defineNuxtConfig({ ssr: false, // Disable SSR for wallet applications vite: { plugins: [ wasm(), topLevelAwait(), nodePolyfills({ include: ['buffer'], globals: { Buffer: true, global: false, process: false } }) ], optimizeDeps: { exclude: ['@hydra-sdk/cardano-wasm'] } } }) ``` ## Vue.js with Vite Configure your `vite.config.js` for Vue projects: 💡 **See the [complete Vue.js example](https://hydrasdk.com/guides/build-wallet-app) for a full working setup.** ```javascript import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import wasm from 'vite-plugin-wasm' import topLevelAwait from 'vite-plugin-top-level-await' import { nodePolyfills } from 'vite-plugin-node-polyfills' export default defineConfig({ plugins: [ vue(), wasm(), topLevelAwait(), nodePolyfills({ include: ['buffer'], globals: { Buffer: true, global: false, process: false } }) ], optimizeDeps: { exclude: ['@hydra-sdk/cardano-wasm'] } }) ``` ## React with Vite For React projects using Vite, update your `vite.config.js`: 💡 **See the [complete React example](https://hydrasdk.com/guides/build-wallet-app) for a full working setup.** ```javascript import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import wasm from 'vite-plugin-wasm' import topLevelAwait from 'vite-plugin-top-level-await' import { nodePolyfills } from 'vite-plugin-node-polyfills' export default defineConfig({ plugins: [ react(), wasm(), topLevelAwait(), nodePolyfills({ include: ['buffer'], globals: { Buffer: true, global: false, process: false } }) ], optimizeDeps: { exclude: ['@hydra-sdk/cardano-wasm'] } }) ``` ## Next.js Configuration Configure Next.js in your `next.config.js`: ```javascript /** @type {import('next').NextConfig} */ const nextConfig = { webpack: (config, { isServer }) => { if (!isServer) { config.experiments = { ...config.experiments, asyncWebAssembly: true, } config.resolve.fallback = { ...config.resolve.fallback, crypto: false, stream: false, util: false, buffer: require.resolve('buffer') } } return config }, // Disable static optimization for wallet pages experimental: { esmExternals: 'loose' } } module.exports = nextConfig ``` For Next.js 13+ with app directory, also create a `globals.d.ts`: ```typescript declare global { var Buffer: typeof import('buffer').Buffer } export {} ``` # Guides These guides are practical, task-oriented walkthroughs. Each one takes you from a goal to working code. For conceptual background see [Concepts](https://hydrasdk.com/concepts); for exact signatures see the [API Reference](https://hydrasdk.com/api). ::card-group :::card --- icon: i-lucide-wallet title: Build a Wallet App to: https://hydrasdk.com/guides/build-wallet-app --- Scaffold a complete wallet application — clone-and-run for React or Vue, or build it yourself with a service-layer architecture. ::: :::card --- icon: i-lucide-file-signature title: Building Transactions to: https://hydrasdk.com/guides/transactions --- Build, sign, and submit transactions — including multi-signature signing and Plutus lock/unlock. ::: :::card --- icon: i-lucide-coins title: Mint & Burn Tokens to: https://hydrasdk.com/guides/mint-burn-tokens --- Create and destroy native tokens with minting policies and metadata. ::: :::card --- icon: i-lucide-network title: Hydra Integration to: https://hydrasdk.com/guides/hydra-integration --- Connect to a Hydra Head, submit transactions, and handle the common pitfalls. ::: :::card --- icon: i-lucide-wrench title: Utilities Cookbook to: https://hydrasdk.com/guides/utilities-cookbook --- Recipes for datums, redeemers, policies, time/slot conversion, and providers. ::: :::card --- icon: i-lucide-key-round title: Import cardano-cli Keys to: https://hydrasdk.com/guides/cardano-cli-keys --- Generate keys with `cardano-cli` and load them into the SDK. ::: :: # Build a Wallet App Build a Cardano wallet application integrated with Hydra Layer 2, from wallet creation through transaction building and Head submission. Start by cloning a ready-made React or Vue example, then graduate to the full service-layer reference implementation when you want to understand every moving part. ## Architecture An end-to-end wallet app is layered on four SDK packages: - **UI** — React, Vue, or Svelte - **Wallet** — `@hydra-sdk/core` (`AppWallet`, accounts, keys) - **Transaction** — `@hydra-sdk/transaction` (`TxBuilder`) - **Hydra L2** — `@hydra-sdk/bridge` (`HydraBridge`) - **WASM** — `@hydra-sdk/cardano-wasm` A production app organizes this into a service layer — one service per concern — with a store binding it to the UI: ```text hydra-wallet-app/ ├── src/ │ ├── services/ │ │ ├── WalletService.ts │ │ ├── HydraService.ts │ │ └── TransactionService.ts │ ├── components/ │ │ ├── WalletManager.vue │ │ ├── HydraConnection.vue │ │ └── TransactionBuilder.vue │ ├── stores/ │ │ └── AppStore.ts │ └── App.vue ├── package.json └── vite.config.ts ``` The core flow is the same regardless of framework: 1. Initialize a wallet with `AppWallet` 2. Get the `account` (address, keys) 3. Build transactions with `TxBuilder` 4. Optional Hydra — use `HydraBridge` to fetch `protocol params` and submit a tx to a Head 5. Handle events and clean up (disconnect) ::note Hydra is optional. For an L1-only wallet you can skip `@hydra-sdk/bridge` entirely and submit transactions to Cardano through your own provider. :: ## Clone and run The fastest path is to clone a ready-made example from the [Hydra SDK Examples](https://github.com/Vtechcom/hydra-sdk-examples){rel=""nofollow""} repository and run it. Pick your framework: ::tabs :::tabs-item{icon="i-simple-icons-react" label="React"} React + Vite starter. Uses `vite.config.js` with the `react()` plugin plus the WASM and polyfill plugins described in [Build it yourself](https://hydrasdk.com/#build-it-yourself). ```bash git clone https://github.com/Vtechcom/hydra-sdk-examples.git cd hydra-sdk-examples/react-app-with-vite ``` ::: :::tabs-item{icon="i-simple-icons-vuedotjs" label="Vue"} Vue 3 + TypeScript + Vite starter. Uses `vite.config.ts` with the `vue()` plugin, `vite-plugin-vue-devtools`, an `@` path alias, and the WASM and polyfill plugins described in [Build it yourself](https://hydrasdk.com/#build-it-yourself). ```bash git clone https://github.com/Vtechcom/hydra-sdk-examples.git cd hydra-sdk-examples/vue-app ``` ::: :: Both examples share the same install and run commands: ```bash pnpm install # or npm install ``` ```bash pnpm dev # or npm run dev ``` Then open `http://localhost:5173` in your browser. The demo generates a new mnemonic, creates an `AppWallet` on the PREPROD testnet, and displays the derived Bech32 address. ### Network configuration Both examples default to the **PREPROD** testnet for development and testing: ```typescript networkId: NETWORK_ID.PREPROD ``` For mainnet, change to: ```typescript networkId: NETWORK_ID.MAINNET ``` ### Troubleshooting Most issues come from the browser build not being configured for WASM. Check these first: 1. **WASM loading errors** — ensure `vite-plugin-wasm` is installed and configured, and that your browser supports WebAssembly. 2. **`Buffer` / `process` not defined** — verify the `vite-plugin-node-polyfills` configuration and that globals are set correctly. 3. **Module resolution errors** — check that all Hydra SDK packages are installed and the Vite config is correct (Vue also needs its path aliases configured). 4. **TypeScript compilation errors** — run `pnpm type-check`, ensure all Hydra SDK types are imported, and verify your `tsconfig`. 5. **Build errors** — ensure `@hydra-sdk/cardano-wasm` is excluded from optimization and all required plugins are in `devDependencies`. ## Build it yourself To go beyond the starter, assemble the service layer yourself. This is the full reference implementation from the complete Hydra wallet app: a `WalletService`, `HydraService`, and `TransactionService`, a Pinia store, and the root Vue component. It applies a service-layer pattern (business logic separated from UI), an event-driven `HydraService`, and centralized reactive state. ::steps{level="3"} ### Install the packages Add the SDK packages and the Vite plugins needed for the browser WASM build: ```bash pnpm add @hydra-sdk/core @hydra-sdk/transaction @hydra-sdk/bridge @hydra-sdk/cardano-wasm ``` ```bash pnpm add vite-plugin-wasm vite-plugin-top-level-await vite-plugin-node-polyfills -D ``` The full `package.json` for the reference app: ```json { "name": "hydra-wallet-app", "version": "1.0.0", "description": "Cardano wallet application with Hydra SDK integration", "scripts": { "dev": "vite", "build": "vue-tsc && vite build", "preview": "vite preview" }, "dependencies": { "@hydra-sdk/core": "^1.0.0", "@hydra-sdk/bridge": "^1.0.0", "@hydra-sdk/transaction": "^1.0.0", "@hydra-sdk/cardano-wasm": "^1.0.0", "vue": "^3.3.0", "pinia": "^2.1.0" }, "devDependencies": { "@vitejs/plugin-vue": "^4.4.0", "typescript": "^5.2.0", "vue-tsc": "^1.8.0", "vite": "^4.4.0", "vite-plugin-wasm": "^3.2.2", "vite-plugin-top-level-await": "^1.3.1", "vite-plugin-node-polyfills": "^0.15.0", "tailwindcss": "^3.3.0", "autoprefixer": "^10.4.0", "postcss": "^8.4.0" } } ``` ### Configure Vite ```typescript // vite.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import wasm from 'vite-plugin-wasm' import topLevelAwait from 'vite-plugin-top-level-await' import { nodePolyfills } from 'vite-plugin-node-polyfills' export default defineConfig({ plugins: [ vue(), wasm(), topLevelAwait(), nodePolyfills({ include: ['buffer'], globals: { Buffer: true, global: false, process: false } }) ], optimizeDeps: { exclude: ['@hydra-sdk/cardano-wasm'] } }) ``` :::tip Each plugin has a job: `vite-plugin-wasm` loads the SDK's WebAssembly crypto, `vite-plugin-top-level-await` enables module-level `await`, and `vite-plugin-node-polyfills` supplies `Buffer` in the browser. Excluding `@hydra-sdk/cardano-wasm` from `optimizeDeps` keeps the WASM package out of dependency pre-bundling. ::: ### Create the wallet service Wraps `AppWallet` for create/restore, account derivation, and balance queries. ```typescript // src/services/WalletService.ts import { AppWallet, NETWORK_ID, UTxO } from '@hydra-sdk/core' export class WalletService { private wallet: AppWallet | null = null private mnemonic: string[] = [] private isInitialized = false async createWallet(mnemonic?: string): Promise { const words = mnemonic ? mnemonic.split(' ') : AppWallet.brew() this.mnemonic = words this.wallet = new AppWallet({ networkId: NETWORK_ID.PREPROD, key: { type: 'mnemonic', words: words } }) this.isInitialized = true return this.wallet } async restoreWallet(mnemonic: string): Promise { return this.createWallet(mnemonic) } getWallet(): AppWallet { if (!this.wallet || !this.isInitialized) { throw new Error('Wallet not initialized') } return this.wallet } getAccount(accountIndex = 0, addressIndex = 0) { const wallet = this.getWallet() return wallet.getAccount(accountIndex, addressIndex) } async getBalance(address?: string): Promise<{ lovelace: string; assets: any[] }> { const wallet = this.getWallet() const account = this.getAccount() const targetAddress = address || account.baseAddressBech32 try { // Assume there's an API to query UTxOs const utxos = await this.queryUTxOs(targetAddress) let lovelaceTotal = 0 const assetMap = new Map() utxos.forEach(utxo => { utxo.output.amount.forEach(asset => { if (asset.unit === 'lovelace') { lovelaceTotal += parseInt(asset.quantity) } else { const current = assetMap.get(asset.unit) || 0 assetMap.set(asset.unit, current + parseInt(asset.quantity)) } }) }) const assets = Array.from(assetMap.entries()).map( ([unit, quantity]) => ({ unit, quantity: quantity.toString() }) ) return { lovelace: lovelaceTotal.toString(), assets } } catch (error) { console.error('Error fetching balance:', error) return { lovelace: '0', assets: [] } } } async queryUTxOs(address: string): Promise { // Integration with Blockfrost, Hexcore, or other services const response = await fetch(`/api/addresses/${address}/utxos`) return response.json() } getMnemonic(): string { this.getWallet() return this.mnemonic.join(' ') } getAddresses() { const account = this.getAccount() return { base: account.baseAddressBech32, stake: account.rewardAddressBech32, enterprise: account.enterpriseAddressBech32 } } isWalletInitialized(): boolean { return this.isInitialized && this.wallet !== null } } export const walletService = new WalletService() ``` :::note `queryUTxOs` here is a placeholder that hits `/api/addresses/:address/utxos`. Swap in your own provider — Blockfrost, Hexcore, or a Cardano node. See the [Utilities Cookbook](https://hydrasdk.com/guides/utilities-cookbook) for helpers. ::: ### Create the Hydra service Extends `EventEmitter` to expose the Head lifecycle as high-level events. It supports both a plain WebSocket `url` and an authenticated `HexcoreConnector`. ```typescript // src/services/HydraService.ts import { HydraBridge, HexcoreConnector } from '@hydra-sdk/bridge' import { EventEmitter } from 'events' export interface HydraConfig { url?: string token?: string useHexcore?: boolean verbose?: boolean } export class HydraService extends EventEmitter { private bridge: HydraBridge | null = null private isConnected = false private headStatus = 'idle' private config: HydraConfig constructor(config: HydraConfig = {}) { super() this.config = { url: 'ws://localhost:4001', useHexcore: false, verbose: true, ...config } } async connect(): Promise { try { if (this.config.useHexcore && this.config.token) { const connector = new HexcoreConnector(this.config.url!, { socketIoOptions: { auth: { token: this.config.token }, transports: ['websocket'], timeout: 20000 }, namespace: 'hydra' }) this.bridge = new HydraBridge({ connector: connector, verbose: this.config.verbose }) } else { this.bridge = new HydraBridge({ url: this.config.url!, verbose: this.config.verbose }) } this.setupEventListeners() await this.bridge.connect() } catch (error) { console.error('Hydra connection error:', error) this.emit('error', error) throw error } } private setupEventListeners(): void { if (!this.bridge) return this.bridge.events.on('onConnected', () => { this.isConnected = true this.emit('connected') }) this.bridge.events.on('onDisconnected', () => { this.isConnected = false this.headStatus = 'disconnected' this.emit('disconnected') }) this.bridge.events.on('onConnectError', (error) => { console.error('Hydra Bridge error:', error) this.emit('error', error) }) this.bridge.events.on('onMessage', (payload) => { this.handleHydraMessage(payload) }) } private handleHydraMessage(payload: any): void { switch (payload.tag) { case 'Greetings': this.headStatus = payload.headStatus?.toLowerCase() || 'idle' this.emit('status', this.headStatus) break case 'HeadIsOpen': this.headStatus = 'open' this.emit('status', this.headStatus) this.emit('headOpen', { parties: payload.parties }) break case 'SnapshotConfirmed': this.emit('snapshot', { number: payload.snapshot.number, utxoCount: Object.keys(payload.snapshot.utxo || {}).length }) break case 'HeadIsClosed': this.headStatus = 'closed' this.emit('status', this.headStatus) this.emit('headClosed') break case 'HeadIsFinalized': this.headStatus = 'finalized' this.emit('status', this.headStatus) this.emit('headFinalized') break case 'TxValid': this.emit('transactionConfirmed', { txId: payload.transaction?.txId, transaction: payload.transaction }) break case 'TxInvalid': this.emit('transactionInvalid', { transaction: payload.transaction, error: payload.validationError }) break default: this.emit('message', payload) } } async initHead(): Promise { if (!this.bridge || !this.isConnected) { throw new Error('Not connected to Hydra Bridge') } if (this.headStatus !== 'idle') { throw new Error(`Cannot init head in state: ${this.headStatus}`) } this.bridge.commands.init() } async closeHead(): Promise { if (!this.bridge || !this.isConnected) { throw new Error('Not connected to Hydra Bridge') } if (this.headStatus !== 'open') { throw new Error(`Cannot close head in state: ${this.headStatus}`) } this.bridge.commands.close() } async submitTransaction(txCbor: string, txId: string): Promise { if (!this.bridge || !this.isConnected) { throw new Error('Not connected to Hydra Bridge') } if (this.headStatus !== 'open') { throw new Error('Head not open for transaction processing') } return this.bridge.submitTxSync({ type: 'Witnessed Tx ConwayEra', description: 'Ledger Cddl Format', cborHex: txCbor, txId }, { timeout: 30000 }) } async queryUTxO(): Promise { if (!this.bridge || !this.isConnected) { throw new Error('Not connected to Hydra Bridge') } return this.bridge.querySnapshotUtxo() } async disconnect(): Promise { if (this.bridge) { await this.bridge.disconnect() this.bridge = null } this.isConnected = false this.headStatus = 'disconnected' } getStatus(): { connected: boolean; headStatus: string } { return { connected: this.isConnected, headStatus: this.headStatus } } } export const hydraService = new HydraService() ``` For deeper Hydra patterns — Hexcore authentication, the balance cache, and the `submitTx` callback API — see the [Hydra Integration](https://hydrasdk.com/guides/hydra-integration) guide. ### Create the transaction service Builds, signs, and submits transactions on both L1 and inside a Hydra Head, and shows a Plutus script spend. ```typescript // src/services/TransactionService.ts import { TxBuilder } from '@hydra-sdk/transaction' import { Deserializer, UTxO } from '@hydra-sdk/core' import { walletService } from './WalletService' import { hydraService } from './HydraService' export interface TransactionRequest { recipient: string amount: string assets?: Array<{ unit: string; quantity: string }> metadata?: any isHydra?: boolean } export class TransactionService { async sendTransaction(request: TransactionRequest): Promise { const wallet = walletService.getWallet() const account = walletService.getAccount() try { // Get UTxOs const utxos = request.isHydra ? await hydraService.queryUTxO() : await walletService.queryUTxOs(account.baseAddressBech32) // Prepare assets to send const outputs = [ { unit: 'lovelace', quantity: request.amount }, ...(request.assets || []) ] // Create TxBuilder const txBuilder = new TxBuilder({ isHydra: request.isHydra, params: request.isHydra ? { minFeeA: 0, minFeeB: 0 } : undefined }) // Build transaction let builder = txBuilder .setInputs(utxos) .txOut(request.recipient, outputs) .changeAddress(account.baseAddressBech32) // Add metadata if present if (request.metadata) { builder = builder.metadataValue(674, request.metadata) } // Set fee (0 for Hydra) if (request.isHydra) { builder = builder.setFee('0') } // Complete transaction const tx = await builder.complete() const txCbor = tx.to_hex() // Sign transaction const signedTx = await wallet.signTx(txCbor, false, 0, 0) const txHash = Deserializer.deserializeTx(signedTx).transaction_hash().to_hex() // Submit transaction if (request.isHydra) { await hydraService.submitTransaction(signedTx, txHash) } else { // Submit to L1 via API await this.submitToL1(signedTx) } return txHash } catch (error) { console.error('Error sending transaction:', error) throw error } } private async submitToL1(txCbor: string): Promise { // Integration with Blockfrost, Hexcore or other services const response = await fetch('/api/transactions/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ txCbor }) }) if (!response.ok) { throw new Error('Transaction submission failed') } } async buildContractTransaction( contractAddress: string, scriptCbor: string, redeemer: any, lockAmount: string ): Promise { const wallet = walletService.getWallet() const account = walletService.getAccount() const utxos = await walletService.queryUTxOs(account.baseAddressBech32) // Find collateral const collateralUTxO = utxos.find(utxo => utxo.output.amount.find(asset => asset.unit === 'lovelace' && Number(asset.quantity) >= 5_000_000 ) ) if (!collateralUTxO) { throw new Error('No suitable collateral UTxO found') } const txBuilder = new TxBuilder() const tx = await txBuilder .setInputs(utxos.filter(u => u !== collateralUTxO)) .txOut(contractAddress, [{ unit: 'lovelace', quantity: lockAmount }]) .txInCollateral( collateralUTxO.input.txHash, collateralUTxO.input.outputIndex, collateralUTxO.output.amount, collateralUTxO.output.address ) .txInScript(scriptCbor) .txInRedeemerValue(redeemer) .changeAddress(account.baseAddressBech32) .complete() const signedTx = await wallet.signTx(tx.to_hex()) await this.submitToL1(signedTx) return Deserializer.deserializeTx(signedTx).transaction_hash().to_hex() } } export const transactionService = new TransactionService() ``` :::warning Hydra transactions use a different fee model. When `isHydra` is true, construct `TxBuilder` with `params: { minFeeA: 0, minFeeB: 0 }` and call `setFee('0')` — otherwise the Head will reject the transaction. See [Transactions](https://hydrasdk.com/guides/transactions) for the L1 fee path, [Mint & Burn Tokens](https://hydrasdk.com/guides/mint-burn-tokens) for minting, and [Smart Contracts in Hydra](https://hydrasdk.com/concepts/smart-contracts-in-hydra) for script spends. ::: ### Add the Pinia store Binds the three services to reactive state, wires up the `HydraService` event listeners, and exposes actions to the UI. ```typescript // src/stores/AppStore.ts import { defineStore } from 'pinia' import { walletService } from '../services/WalletService' import { hydraService } from '../services/HydraService' import { transactionService } from '../services/TransactionService' export const useAppStore = defineStore('app', { state: () => ({ // Wallet state isWalletInitialized: false, walletAddresses: null as any, balance: { lovelace: '0', assets: [] } as any, // Hydra state isHydraConnected: false, headStatus: 'idle', hydraMessages: [] as any[], // Transaction state pendingTransactions: [] as string[], transactionHistory: [] as any[], // UI state loading: false, error: null as string | null }), getters: { formattedBalance: (state) => { const ada = (parseInt(state.balance.lovelace) / 1_000_000).toFixed(6) return `${ada} ADA` }, canInitHead: (state) => { return state.isHydraConnected && state.headStatus === 'idle' }, canSendHydraTransaction: (state) => { return state.isHydraConnected && state.headStatus === 'open' } }, actions: { // Wallet actions async createWallet(mnemonic?: string) { this.loading = true this.error = null try { await walletService.createWallet(mnemonic) this.isWalletInitialized = true this.walletAddresses = walletService.getAddresses() await this.updateBalance() } catch (error: any) { this.error = error.message throw error } finally { this.loading = false } }, async updateBalance() { if (!this.isWalletInitialized) return try { this.balance = await walletService.getBalance() } catch (error: any) { console.error('Error updating balance:', error) } }, // Hydra actions async connectHydra(config?: any) { this.loading = true this.error = null try { if (config) { Object.assign(hydraService.config, config) } // Setup event listeners hydraService.on('connected', () => { this.isHydraConnected = true }) hydraService.on('disconnected', () => { this.isHydraConnected = false this.headStatus = 'disconnected' }) hydraService.on('status', (status) => { this.headStatus = status }) hydraService.on('message', (message) => { this.hydraMessages.unshift({ ...message, timestamp: new Date().toISOString() }) // Keep only the last 50 messages if (this.hydraMessages.length > 50) { this.hydraMessages = this.hydraMessages.slice(0, 50) } }) hydraService.on('error', (error) => { this.error = error.message }) await hydraService.connect() } catch (error: any) { this.error = error.message throw error } finally { this.loading = false } }, async disconnectHydra() { await hydraService.disconnect() hydraService.removeAllListeners() }, async initHead() { try { await hydraService.initHead() } catch (error: any) { this.error = error.message throw error } }, async closeHead() { try { await hydraService.closeHead() } catch (error: any) { this.error = error.message throw error } }, // Transaction actions async sendTransaction(request: any) { this.loading = true this.error = null try { const txHash = await transactionService.sendTransaction(request) this.pendingTransactions.push(txHash) // Update balance after sending setTimeout(() => this.updateBalance(), 2000) return txHash } catch (error: any) { this.error = error.message throw error } finally { this.loading = false } }, // Utility actions clearError() { this.error = null }, clearMessages() { this.hydraMessages = [] } } }) ``` ### Wire up the App component The root component reads store state, renders wallet/Hydra/balance status, and cleans up the auto-refresh interval and Hydra connection on unmount. ```vue ``` :: Wire the `WalletManager`, `HydraConnection`, and `TransactionBuilder` child components to the store actions and getters shown above, and you have a full wallet, Hydra, and transaction UI. ## Next steps - [Transactions](https://hydrasdk.com/guides/transactions) — build, sign, and submit L1 and L2 transactions in depth - [Hydra Integration](https://hydrasdk.com/guides/hydra-integration) — connectors, the balance cache, and the `submitTx` callback API - [Mint & Burn Tokens](https://hydrasdk.com/guides/mint-burn-tokens) — native asset minting and burning - [Utilities Cookbook](https://hydrasdk.com/guides/utilities-cookbook) — serialization, address, and UTxO helpers - [Core API](https://hydrasdk.com/api/core) and [Bridge API](https://hydrasdk.com/api/bridge) — full reference for `AppWallet` and `HydraBridge` # Building Transactions The `TxBuilder` from `@hydra-sdk/transaction` gives you a fluent API for assembling Cardano transactions: selecting inputs, adding outputs, attaching scripts and redeemers, and completing an unsigned transaction. This guide walks through the core flows: building and submitting a simple transfer, signing (including multi-signature), and locking or unlocking assets with Plutus scripts. ## Build and submit a transaction The basic flow is always the same: fetch UTxOs for your address, feed them to the `TxBuilder`, add your outputs, set a change address, and call `.complete()` to get an unsigned transaction. You then sign it with your wallet and submit it through a provider. ### 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 (real-world example) 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 from Blockfrost const utxos = await blockfrostProvider.fetcher.fetchAddressUTxOs(baseAddressBech32) console.log('>>> UTxOs found:', utxos.length) // Build transaction const txBuilder = 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 The same build flow works with any provider. Swap the provider and use its `submitter` to broadcast the signed transaction: ```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 from Ogmios const utxos = await ogmiosProvider.fetcher.fetchAddressUTxOs(baseAddressBech32) console.log('>>> UTxOs found:', JSON.stringify(utxos, null, 1)) const txBuilder = 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 with Ogmios const txHash = await ogmiosProvider.submitter.submitTx(signedTx) console.log('>>> Submitted Tx Hash:', txHash) } ``` ## Signing transactions `wallet.signTx()` takes an unsigned transaction as CBOR hex and returns the signed CBOR. Once signed, you can deserialize it to read the transaction hash before submitting. ### Single signature ```typescript import { AppWallet, NETWORK_ID, Deserializer } from '@hydra-sdk/core' const wallet = new AppWallet({ key: { type: 'mnemonic', words: 'your mnemonic words here'.split(' ') }, networkId: NETWORK_ID.PREPROD }) async function signTransaction(cborHex: string, partialSign: boolean = false) { try { const signedTx = await wallet.signTx(cborHex, partialSign) const txHash = Deserializer.deserializeTx(signedTx).transaction_hash().to_hex() console.log('✅ Transaction signed:', txHash) return { signedTx, txHash } } catch (error) { console.error('❌ Signing error:', error) throw error } } // Usage const result = await signTransaction(transactionCborHex) ``` ### Multi-signature When a transaction requires signatures from multiple wallets, chain the signing calls: each signer feeds the CBOR produced by the previous one back into `signTx`. ::note Pass `partialSign: true` for every signer except the last. Partial signing appends a witness without finalizing the transaction, so the next signer can add theirs. The final signer signs without `partialSign` to complete the transaction. :: ```typescript import { AppWallet, NETWORK_ID, Deserializer } from '@hydra-sdk/core' class MultiSigWallet { private wallets: AppWallet[] = [] addSigner(mnemonic: string[]) { const wallet = new AppWallet({ key: { type: 'mnemonic', words: mnemonic }, networkId: NETWORK_ID.PREPROD }) this.wallets.push(wallet) } async signWithAllWallets(cborHex: string) { let currentCbor = cborHex for (let i = 0; i < this.wallets.length; i++) { const isLastSigner = i === this.wallets.length - 1 currentCbor = await this.wallets[i].signTx(currentCbor, !isLastSigner) console.log(`✅ Wallet ${i + 1}/${this.wallets.length} signed`) } return { finalTx: currentCbor, txHash: Deserializer.deserializeTx(currentCbor).transaction_hash().to_hex() } } } // Usage const multiSig = new MultiSigWallet() multiSig.addSigner('first signer mnemonic'.split(' ')) multiSig.addSigner('second signer mnemonic'.split(' ')) const result = await multiSig.signWithAllWallets(unsignedTxCbor) ``` ### Performance Pattern For high-throughput scenarios, wrap signing in a small helper that records timing and transaction shape. This lets you track signing latency and spot regressions across many transactions. ```typescript import { AppWallet, NETWORK_ID, Deserializer } from '@hydra-sdk/core' class PerformanceSigner { private wallet: AppWallet private metrics: Array<{ timestamp: number duration: number txHash: string inputCount: number outputCount: number }> = [] constructor(mnemonic: string[]) { this.wallet = new AppWallet({ key: { type: 'mnemonic', words: mnemonic }, networkId: NETWORK_ID.PREPROD }) } async signWithMetrics(cborHex: string) { const startTime = Date.now() try { const signedTx = await this.wallet.signTx(cborHex) const duration = Date.now() - startTime const tx = Deserializer.deserializeTx(signedTx) const txHash = tx.transaction_hash().to_hex() const body = tx.transaction_body() this.metrics.push({ timestamp: startTime, duration, txHash, inputCount: body.inputs().len(), outputCount: body.outputs().len() }) console.log(`⏱️ Signing took ${duration}ms: ${txHash}`) return { signedTx, txHash, duration } } catch (error) { const duration = Date.now() - startTime console.error(`❌ Signing failed after ${duration}ms:`, error) throw error } } getPerformanceStats() { if (this.metrics.length === 0) return null const durations = this.metrics.map(m => m.duration) const avg = durations.reduce((a, b) => a + b, 0) / durations.length return { totalTransactions: this.metrics.length, averageDuration: Math.round(avg), minDuration: Math.min(...durations), maxDuration: Math.max(...durations) } } } // Usage const performanceSigner = new PerformanceSigner('your mnemonic words here'.split(' ')) await performanceSigner.signWithMetrics(tx1) await performanceSigner.signWithMetrics(tx2) const stats = performanceSigner.getPerformanceStats() console.log('Stats:', stats) ``` ## Locking and unlocking with Plutus scripts Plutus scripts let you lock assets at a contract address and release them only when a valid redeemer is supplied. Locking is a plain output to the script address plus an inline datum. Unlocking is more involved: you spend the script UTxO as an input, attach the redeemer and script, and provide collateral. ### Lock assets in a contract script This example locks 2 ADA at a Plutus script address, attaching an inline datum that the unlock step will validate against. ```typescript import { AppWallet, DatumUtils, Deserializer, NETWORK_ID, ParserUtils, ProviderUtils, 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.mkBytes(ParserUtils.stringToHex("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 = Deserializer.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() ``` ### Unlock assets from a contract script Unlocking assumes an asset is already locked (via the example above). You supply the script UTxO as an input, a redeemer, the Plutus script itself, and a collateral UTxO to cover script-execution failure. ::warning Unlocking a Plutus script requires a collateral UTxO holding at least 5 ADA. Collateral is only consumed if script validation fails, but it must be present for the transaction to be accepted. :: ```typescript import { AppWallet, Deserializer, NETWORK_ID, ProviderUtils, 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 = Deserializer.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') ``` ### Key points **Lock transaction** - Sends assets to the contract address with `.txOut()`. - Attaches an inline datum with `.txOutInlineDatumValue()`. - No special script handling is required for locking. **Unlock transaction** - Requires a collateral UTxO (minimum 5 ADA). - Uses the script UTxO as an input with `.txIn()`. - Attaches the redeemer with `.txInRedeemerValue()`. - Attaches the Plutus script with `.txInScript()`. - Sets collateral with `.txInCollateral()`. - Validates collateral availability and script UTxO existence before building. ## Minting and burning tokens The `TxBuilder` also supports minting and burning native tokens. You attach a native minting policy with `.mintingScript()`, call `.mint()` with a positive quantity to mint or a negative quantity to burn, and optionally attach CIP-25 token metadata. Because these flows have their own dedicated walkthrough, they are documented separately. ::tip See the [Mint and Burn Tokens guide](https://hydrasdk.com/guides/mint-burn-tokens) for the complete minting and burning workflow, including policy creation, metadata, and collateral handling. :: ## High-volume building (v1.2.0+) Every CSL object lives in WASM memory and is only reclaimed by `.free()`. For one-off transactions the default `complete()` is fine, but when you build thousands in a row (e.g. inside a Hydra Head), use the leak-free helpers added in `@hydra-sdk/transaction@1.2.0`: - **`completeCbor()`** — builds and returns the CBOR hex, freeing the intermediate `Transaction` for you. Prefer it over `complete()` + `.to_hex()` when you only need the bytes. - **`dispose()` / `using`** — release all WASM memory a builder holds; reuse one builder with `reset()` across a spike and `dispose()` at the end. ```typescript import { TxBuilder } from '@hydra-sdk/transaction' // Reuse one builder across many transactions, then dispose once const builder = new TxBuilder() for (const job of jobs) { const cbor = await builder .reset() .setInputs(job.utxos) .txOut(job.address, job.amount) .changeAddress(job.changeAddress) .completeCbor() await submit(cbor) } builder.dispose() ``` See [Performance → TxBuilder WASM Memory](https://hydrasdk.com/resources/performance#txbuilder-wasm-memory-v120) for benchmarks and the full pattern list. ## Next steps - [Mint and Burn Tokens](https://hydrasdk.com/guides/mint-burn-tokens) — create and destroy native tokens with metadata. - [Utilities Cookbook](https://hydrasdk.com/guides/utilities-cookbook) — helpers for datums, policies, addresses, and parsing. - [Transaction API reference](https://hydrasdk.com/api/transaction) — the full `TxBuilder` and redeemer API. - [Transactions in Hydra](https://hydrasdk.com/concepts/transactions-in-hydra) — how transactions differ inside a Hydra Head. # Mint and Burn Tokens Guide This comprehensive guide teaches you how to mint (create) and burn (destroy) native tokens on the Cardano blockchain using Hydra SDK. You'll learn to create custom tokens with metadata, manage minting policies, and properly burn tokens when needed. ## Prerequisites Before starting, ensure you have: - Basic understanding of Cardano blockchain concepts - Node.js and TypeScript development environment - Hydra SDK installed and configured - Access to Cardano testnet (Preprod) for testing ## Project Setup ### Install Dependencies ```bash npm install @hydra-sdk/core @hydra-sdk/transaction @hydra-sdk/cardano-wasm ``` ### Import Required Modules ```typescript import { AppWallet, DatumUtils, Deserializer, NETWORK_ID, ParserUtils, PolicyUtils, Serializer } from '@hydra-sdk/core' import { TxBuilder } from '@hydra-sdk/transaction' import { CardanoWASM } from '@hydra-sdk/cardano-wasm' ``` ## Wallet Setup First, create a wallet instance for signing transactions: ```typescript const wallet = new AppWallet({ key: { type: 'mnemonic', words: 'your twelve word mnemonic phrase goes here like this example'.split(' ') }, networkId: NETWORK_ID.PREPROD // Use PREPROD for testing }) const walletAddress = wallet.getAccount().baseAddressBech32 console.log('Wallet Address:', walletAddress) ``` ::warning **Security Note**: Never use real mnemonic phrases in production code. Use environment variables or secure key management. :: ## Minting Tokens ### Step 1: Query UTxOs and Setup Collateral ```typescript async function mintToken() { // Query available UTxOs console.log('>>> Querying UTxO...', walletAddress) const utxos = await wallet.queryUTxOs(walletAddress) console.log(`>>> Found ${utxos.length} UTxOs`) // Find suitable collateral UTxO (>= 5 ADA) 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] // ...continued below } ``` ### Step 2: Create Minting Policy and Token Details ```typescript // Create minting policy from wallet address const scriptCborHex = PolicyUtils.buildMintingPolicyScriptFromAddress(walletAddress) const policyId = PolicyUtils.policyIdFromNativeScript(scriptCborHex) const assetNameHex = ParserUtils.stringToHex('AniaToken') // Define token metadata (CIP-25 standard) 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('Asset Name (hex):', assetNameHex) ``` ### Step 3: Build and Submit Minting Transaction ```typescript const txBuilder = new TxBuilder() const tx = await txBuilder // Set inputs (exclude collateral UTxO) .setInputs( utxos.filter(u => `${u.input.txHash}#${u.input.outputIndex}` !== `${collateralUTxO.input.txHash}#${collateralUTxO.input.outputIndex}` ) ) // Mint 1,000,000 tokens (6 decimal places = 1 token) .mint('1000000', policyId, assetNameHex) // Attach minting script .mintingScript({ type: 'Native', scriptCborHex: scriptCborHex }) // Add metadata (CIP-25 for NFTs/tokens) .metadataValue(721, { [policyId]: { [assetNameHex]: { ...assetMetadata } } }) // Set collateral for script execution .txInCollateral( collateralUTxO.input.txHash, collateralUTxO.input.outputIndex, collateralUTxO.output.amount, collateralUTxO.output.address ) // Output: send minted tokens to wallet .addOutput({ address: walletAddress, amount: [ { unit: 'lovelace', quantity: String(2_000_000) }, { unit: Serializer.serializeAssetUnit(policyId, assetNameHex), quantity: '1000000' } ] }) .changeAddress(walletAddress) .complete() // Sign and submit transaction const signedCbor = await wallet.signTx(tx.to_hex()) console.log('Signed Transaction:', signedCbor) console.log('Transaction ID:', Deserializer.deserializeTx(signedCbor).transaction_hash().to_hex()) ``` ## Burning Tokens Burning tokens permanently removes them from circulation. This is useful for deflationary mechanisms or removing unwanted tokens. ### Complete Burn Implementation ```typescript async function burnToken() { // Query UTxOs (same as minting) const utxos = await wallet.queryUTxOs(walletAddress) 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] // Same policy and asset details const scriptCborHex = PolicyUtils.buildMintingPolicyScriptFromAddress(walletAddress) const policyId = PolicyUtils.policyIdFromNativeScript(scriptCborHex) const assetNameHex = ParserUtils.stringToHex('AniaToken') const txBuilder = new TxBuilder() const tx = await txBuilder .setInputs( utxos.filter(u => `${u.input.txHash}#${u.input.outputIndex}` !== `${collateralUTxO.input.txHash}#${collateralUTxO.input.outputIndex}` ) ) // Negative amount = burn tokens .mint('-1000000', policyId, assetNameHex) .mintingScript({ type: 'Native', scriptCborHex: scriptCborHex }) .changeAddress(walletAddress) .complete() const signedCbor = await wallet.signTx(tx.to_hex()) console.log('Burn Transaction ID:', Deserializer.deserializeTx(signedCbor).transaction_hash().to_hex()) } ``` ## Advanced: working with datums Minting or locking a token often means attaching a structured datum built from nested constructors and maps. That pattern has a single canonical home in the utilities cookbook, so it stays in sync across guides. ::tip See [Utilities Cookbook → Building datums](https://hydrasdk.com/guides/utilities-cookbook#building-datums) for the complete `buildDatum` helper and other datum-construction recipes. :: ## Important Considerations ### Security Best Practices 1. **Test on Preprod First**: Always test your minting/burning logic on testnet 2. **Validate Inputs**: Check UTxO availability and amounts before building transactions 3. **Handle Errors**: Implement proper error handling for network and transaction failures 4. **Secure Key Management**: Use hardware wallets or secure key storage in production ### Common Pitfalls 1. **Insufficient Collateral**: Ensure you have enough ADA for collateral (≥5 ADA) 2. **Policy ID Consistency**: Use the same policy for minting and burning the same token 3. **Asset Name Encoding**: Remember to convert asset names to hex format 4. **UTxO Selection**: Properly exclude collateral UTxOs from transaction inputs ### Gas and Fees - Minting transactions require higher fees due to script execution - Collateral UTxOs are returned if transaction succeeds - Consider network congestion when setting fees ## Complete Example Here's the complete working example: ```typescript import { AppWallet, DatumUtils, Deserializer, NETWORK_ID, ParserUtils, PolicyUtils, Serializer } from '@hydra-sdk/core' import { TxBuilder } from '@hydra-sdk/transaction' import { CardanoWASM } from '@hydra-sdk/cardano-wasm' // Initialize wallet 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 }) const walletAddress = wallet.getAccount().baseAddressBech32 // Execute minting or burning async function main() { console.log('Wallet Address:', walletAddress) // Uncomment the operation you want to perform await mintToken() // await burnToken() } main().catch(console.error) ``` ## Next Steps After mastering token minting and burning, explore: - **NFT Creation**: Learn to create unique non-fungible tokens - **Multi-signature Policies**: Implement policies requiring multiple signatures - **Time-locked Policies**: Create tokens with time-based minting restrictions - **Plutus Scripts**: Advanced scripting for complex minting logic --- *This guide provides a foundation for token operations on Cardano. Always test thoroughly and follow security best practices in production environments.* # Hydra Integration Examples Examples based on real integration patterns used in the Hydra SDK ecosystem. ## Basic Hydra Transaction Building ```typescript import { TxBuilder } from '@hydra-sdk/transaction' // Hydra transactions have different fees from Cardano mainnet const txBuilder = new TxBuilder({ isHydra: true, params: { minFeeA: 0, // Hydra uses a different fee structure minFeeB: 0 } }) // Build the transaction as usual const tx = await txBuilder .setInputs(utxos) .addOutput({ address: recipientAddress, amount: [{ unit: 'lovelace', quantity: '1000000' }] }) .setChangeAddress(changeAddress) .complete() console.log('Hydra Transaction:', tx.to_hex()) ``` ## Hydra Bridge Connection (From Real Usage Patterns) ```typescript import { HydraBridge, HexcoreConnector } from '@hydra-sdk/bridge' // Socket.IO connection with authentication const bridge = new HydraBridge({ connector: new HexcoreConnector( 'wss://alpha-v1-api.hexcore.io.vn', { socketIoOptions: { auth: { token: 'your-jwt-token-here' } } } ) }) // Event handlers following real-world patterns bridge.events.on('onConnected', () => { console.log('✅ Connected to Hydra Head') }) bridge.events.on('onDisconnected', () => { console.log('❌ Disconnected from Hydra Head') }) // Head/Tx state is delivered through onMessage — switch on payload.tag bridge.events.on('onMessage', (payload) => { switch (payload.tag) { case 'HeadIsOpen': console.log('🚀 Hydra Head is open:', payload) break case 'HeadIsClosed': console.log('🔒 Hydra Head is closed:', payload) break case 'TxValid': console.log('✅ Transaction valid:', payload.transaction) break case 'TxInvalid': console.log('❌ Transaction invalid:', payload.transaction, payload.validationError) break } }) // Connect and clean up await bridge.connect() // Cleanup trong Vue/React lifecycle // onBeforeUnmount(() => bridge?.disconnect()) ``` ## Custom API Integration với Hydra ```typescript // Pattern from nodejs-playground for Hydra API calls import { Converter, UTxO } from '@hydra-sdk/core' import axios from 'axios' class HydraHexcoreApi { private instance = axios.create({ baseURL: 'https://alpha-v1-api.hexcore.io.vn', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.HEXCORE_API_TOKEN}` } }) async queryAddressUTxO(address: string): Promise { try { const response = await this.instance.get(`hydra-main/utxo/${address}`) if (response.data.data) { return Converter.convertUTxOObjectToUTxO(response.data.data) } else { throw new Error('No UTxO data found in Hydra response') } } catch (error) { console.error('Error querying Hydra UTxO:', error) throw error } } async submitHydraTx(cborHex: string) { try { const response = await this.instance.post('/hydra-main/tx', { cborHex }) return response.data.txHash } catch (error) { console.error('Error submitting to Hydra:', error) throw error } } } // Usage const hydraApi = new HydraHexcoreApi() const utxos = await hydraApi.queryAddressUTxO(walletAddress) ``` ## Environment-specific Configuration ```typescript // Pattern cho environment configuration import { NETWORK_ID } from '@hydra-sdk/core' const getNetworkConfig = (isHydra: boolean = false) => { if (isHydra) { return { networkId: NETWORK_ID.PREPROD, // Hydra uses the preprod network apiEndpoint: 'https://alpha-v1-api.hexcore.io.vn', socketEndpoint: 'wss://alpha-v1-api.hexcore.io.vn', isHydra: true, txParams: { minFeeA: 0, minFeeB: 0 } } } else { return { networkId: NETWORK_ID.PREPROD, apiEndpoint: 'https://preprod.blockfrost.io/api/v0', socketEndpoint: null, isHydra: false, txParams: { minFeeA: 44, minFeeB: 155381 } } } } // Usage trong application const config = getNetworkConfig(process.env.USE_HYDRA === 'true') const txBuilder = new TxBuilder({ isHydra: config.isHydra, params: config.txParams }) ``` ## Complete Hydra Integration Example ```typescript import { AppWallet, NETWORK_ID } from '@hydra-sdk/core' import { TxBuilder } from '@hydra-sdk/transaction' import { HydraBridge, HexcoreConnector } from '@hydra-sdk/bridge' class HydraWalletManager { private wallet: AppWallet private bridge: HydraBridge private hydraApi: HydraHexcoreApi constructor(mnemonic: string[], jwtToken: string) { this.wallet = new AppWallet({ key: { type: 'mnemonic', words: mnemonic }, networkId: NETWORK_ID.PREPROD }) this.bridge = new HydraBridge({ connector: new HexcoreConnector( 'wss://alpha-v1-api.hexcore.io.vn', { socketIoOptions: { auth: { token: jwtToken } } } ) }) this.hydraApi = new HydraHexcoreApi() this.setupEventHandlers() } private setupEventHandlers() { this.bridge.events.on('onConnected', () => { console.log('🔗 Hydra Bridge Connected') }) this.bridge.events.on('onMessage', (payload) => { if (payload.tag === 'TxValid') { console.log('✅ Hydra TX Valid:', payload.transaction) } }) } async sendHydraTransaction(recipientAddress: string, amountLovelace: string) { try { // 1. Connect to Hydra if not connected if (!this.bridge.connected()) { await this.bridge.connect() } // 2. Query UTxOs from Hydra const walletAddress = this.wallet.getAccount().baseAddressBech32 const utxos = await this.hydraApi.queryAddressUTxO(walletAddress) // 3. Build Hydra transaction const txBuilder = new TxBuilder({ isHydra: true, params: { minFeeA: 0, minFeeB: 0 } }) const tx = await txBuilder .setInputs(utxos) .addOutput({ address: recipientAddress, amount: [{ unit: 'lovelace', quantity: amountLovelace }] }) .setChangeAddress(walletAddress) .complete() // 4. Sign transaction const signedTx = await this.wallet.signTx(tx.to_hex()) // 5. Submit to Hydra const txHash = await this.hydraApi.submitHydraTx(signedTx) console.log('🚀 Hydra Transaction Submitted:', txHash) return txHash } catch (error) { console.error('❌ Hydra Transaction Error:', error) throw error } } async disconnect() { await this.bridge.disconnect() } } // Usage const hydraManager = new HydraWalletManager( 'your mnemonic words here'.split(' '), 'your-jwt-token' ) await hydraManager.sendHydraTransaction( 'addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer', '1000000' ) ``` ## submitTx — Callback API (v1.3.0+) Non-blocking fire-and-forget submission using Node.js error-first callback pattern: ```typescript import { HydraBridge } from '@hydra-sdk/bridge' const bridge = new HydraBridge({ url: 'ws://localhost:4001' }) await bridge.connect() // submitTx returns void immediately — result delivered via callback bridge.submitTx( { type: 'Witnessed Tx ConwayEra', description: '', cborHex: signedTxCbor, txId: transactionId }, (error, result) => { if (error) { console.error('Tx rejected:', error.reason, 'tag:', error.tag) return } console.log('Confirmed in snapshot #', result!.result?.snapshot?.number) console.log('isValid:', result!.isValid) }, { timeout: 30000 } ) ``` ### When to use `submitTx` vs `submitTxSync` | | `submitTxSync` | `submitTx` | | -------- | ------------------------------- | ---------------------------- | | Returns | `Promise` | `void` | | Usage | `await bridge.submitTxSync(tx)` | callback-based | | Best for | Sequential flows, `async/await` | Event loops, fire-and-forget | --- ## In-Memory Balance Cache (v1.3.0+) After the first `Greetings` or `SnapshotConfirmed`, balance reads are O(1): ```typescript bridge.events.on('onMessage', payload => { if (payload.tag === 'SnapshotConfirmed') { // Cache is updated automatically const balance = bridge.getAddressBalance('addr_test1...') if (balance === null) return // cold start, cache not seeded yet const lovelace = balance.get('lovelace') ?? 0n console.log('Balance:', Number(lovelace) / 1_000_000, 'ADA') } }) ``` --- ## Common Hydra Pitfalls và Solutions ### 1. Wrong Transaction Mode ```typescript // ❌ Wrong - using Cardano params for Hydra const txBuilder = new TxBuilder({}) // Default Cardano params // ✅ Correct - using Hydra params const txBuilder = new TxBuilder({ isHydra: true, params: { minFeeA: 0, minFeeB: 0 } }) ``` ### 2. Socket Connection Issues ```typescript // ❌ Wrong - using plain WebSocket const ws = new WebSocket('wss://alpha-v1-api.hexcore.io.vn') // ✅ Correct - using Socket.IO with auth const bridge = new HydraBridge({ connector: new HexcoreConnector(url, { socketIoOptions: { auth: { token: 'your-jwt-token' } } }) }) ``` ### 3. Memory Leaks ```typescript // ✅ Proper cleanup class HydraComponent { private bridge: HydraBridge async mounted() { this.bridge = new HydraBridge({...}) await this.bridge.connect() } async beforeUnmount() { // Important: clean up the connection await this.bridge?.disconnect() } } ``` # 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](https://hydrasdk.com/api/utilities). ## Building datums Construct Plutus data with the `DatumUtils` encoders. Start with the primitives, then compose them into structured, nested datums. ```typescript 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. ::note This `buildDatum` block is also used in the [Mint and Burn Tokens](https://hydrasdk.com/guides/mint-burn-tokens) guide, which links back here as the canonical source. Keep the two in sync. :: ```typescript 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 ::note New in v1.4.0. Convenience encoders for lists, booleans, options, output references, and addresses. :: ```typescript 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. ```typescript import { DatumUtils, ParserUtils } from '@hydra-sdk/core' const createNFTDatum = (name: string, image: string, attributes: Record) => { 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`. ::note New in v1.4.0. :: ```typescript 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`. ```typescript 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. ```typescript 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`. ```typescript 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. ```typescript 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. ```typescript 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 ::note New in v1.4.0. `deserializeAmountsFromTx` sums every output's amounts, merged by unit. :: ```typescript 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. ::note Providers do not expose protocol parameters. Inside a Hydra Head, read them from the bridge instead: `await bridge.getProtocolParameters()`. :: ```typescript 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 ::note New in v1.4.0. `DemeterProvider` extends `BlockfrostProvider`; same `.fetcher` / `.submitter` surface. :: ```typescript 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`. ::note New in v1.4.0. :: ```typescript 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. ```typescript 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' ``` ::tip The same `try/catch` wrapper works for any utility call — for example wrap `TimeUtils.unixTimeToEnclosingSlot(...)` to guard against invalid timestamps. :: ## See also - [/api/utilities](https://hydrasdk.com/api/utilities) — full function signatures for every utility namespace. - [/guides/transactions](https://hydrasdk.com/guides/transactions) — build and submit transactions using these utilities. - [/guides/mint-burn-tokens](https://hydrasdk.com/guides/mint-burn-tokens) — end-to-end minting flow that reuses the `buildDatum` recipe above. # Integrate from Cardano-CLI Learn how to use keys generated from Cardano-CLI with Hydra SDK. The `CardanoCliWallet` class allows you to use existing CLI keys in your Node.js applications for programmatic transaction signing and blockchain interactions. **Reference**: [Cardano-CLI Documentation](https://developers.cardano.org/docs/get-started/cardano-cli/basic-operations/get-started){rel=""nofollow""} ## Generate Keys with Cardano-CLI Generate a payment key pair using Cardano-CLI: ```bash cardano-cli address key-gen \ --verification-key-file payment.vkey \ --signing-key-file payment.skey ``` This will create two files: **`payment.vkey`** (Verification Key): ```json { "type": "PaymentVerificationKeyShelley_ed25519", "description": "Payment Verification Key", "cborHex": "5820832ba166c8ba8afda5b9d85dfe13dd8fffd460da79a2c3cf34e107216637985b" } ``` **`payment.skey`** (Signing Key): ```json { "type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820bd09ad4f98cd103e059ab62d17a6a7d920b16d9f0eed3eb6b77d3ca8f61dc117" } ``` ::warning ⚠️ **Security Warning**: Never share your signing key (`payment.skey`) or commit it to version control. :: ## Verify Keys (Optional) Verify your keys by building an address: ```bash cardano-cli address build \ --payment-verification-key-file payment.vkey \ --testnet-magic 1 \ --out-file payment.addr ``` **Output** (`payment.addr`): ```text addr_test1vz5hhyn6ecl66a2ca3cwfnwu8ddnp24hakfq2k37rhk28ysk8g0wz ``` ::note This is an **enterprise address** (payment-only, no staking key) following [CIP-19](https://cips.cardano.org/cips/cip19/){rel=""nofollow""}. :: ## Use with Hydra SDK ### Basic Setup ```ts import { CardanoCliWallet, NETWORK_ID } from '@hydra-sdk/core'; // Extract the cborHex values from your key files const skey = '5820bd09ad4f98cd103e059ab62d17a6a7d920b16d9f0eed3eb6b77d3ca8f61dc117'; const vkey = '5820832ba166c8ba8afda5b9d85dfe13dd8fffd460da79a2c3cf34e107216637985b'; // Initialize the wallet const wallet = new CardanoCliWallet({ skey, vkey, networkId: NETWORK_ID.PREPROD }); // Get address in Bech32 format const address = wallet.getAddressBech32(); console.log('Wallet Address:', address); // Output: addr_test1vz5hhyn6ecl66a2ca3cwfnwu8ddnp24hakfq2k37rhk28ysk8g0wz ``` ### Transaction Example ```ts import { CardanoCliWallet, NETWORK_ID, ProviderUtils } from '@hydra-sdk/core'; import { TxBuilder } from '@hydra-sdk/transaction'; // Initialize wallet with CLI keys const wallet = new CardanoCliWallet({ skey: '5820bd09ad4f98cd103e059ab62d17a6a7d920b16d9f0eed3eb6b77d3ca8f61dc117', vkey: '5820832ba166c8ba8afda5b9d85dfe13dd8fffd460da79a2c3cf34e107216637985b', networkId: NETWORK_ID.PREPROD }); // Setup provider const provider = new ProviderUtils.BlockfrostProvider({ apiKey: 'your-blockfrost-project-id', network: 'preprod' }); // Build transaction const txBuilder = new TxBuilder({ fetcher: provider.fetcher, submitter: provider.submitter }); const unsignedTx = await txBuilder // ... .complete(); // Sign transaction const signedTxCbor = await wallet.signTx(unsignedTx.to_hex()); console.log('Signed Transaction:', signedTxCbor); ``` ## API Reference ### Constructor ```ts new CardanoCliWallet(options: CardanoCliWalletConfig) ``` **Options**: - `skey` (required): CBOR hex of the signing key (format: `5820...`) - `vkey` (required): CBOR hex of the verification key (format: `5820...`) - `networkId` (optional): `NETWORK_ID.MAINNET` (1) or `NETWORK_ID.PREPROD` (0), default is MAINNET - `fetcher` (optional): Custom fetcher for querying UTxOs - `submitter` (optional): Custom submitter for transaction submission ### Methods ```ts // Get address in Bech32 format getAddressBech32(): string // Sign a transaction signTx(unsignedTxHex: string, partialSign?: boolean): Promise // Get network ID getNetworkId(): number // Submit transaction (requires submitter in constructor) submitTx(tx: string): Promise // Query UTxOs (requires fetcher in constructor) queryUTxOs(address: string): Promise ``` ### Properties ```ts // Get payment signing key paymentSKey: CardanoWASM.PrivateKey // Get payment verification key paymentVKey: CardanoWASM.PublicKey ``` ## Next Steps - Learn about [Building Transactions](https://hydrasdk.com/guides/transactions) - Explore [Working with Utilities](https://hydrasdk.com/guides/utilities-cookbook) - See more [Examples](https://hydrasdk.com/guides) ## Related Resources - [Cardano-CLI Documentation](https://developers.cardano.org/docs/get-started/cardano-cli/basic-operations/get-started){rel=""nofollow""} - [CIP-19: Cardano Addresses](https://cips.cardano.org/cips/cip19/){rel=""nofollow""} - [Hydra SDK Core API](https://hydrasdk.com/api/core) # Why Hydra? Hydra is Cardano's Layer 2 scaling solution that addresses the blockchain trilemma by providing high throughput, low latency, and minimal transaction costs while maintaining security and decentralization. ## The Scalability Challenge Blockchain networks face fundamental scalability limitations: - **Limited Throughput** - Layer 1 blockchains process transactions slowly (Cardano mainnet: \~250 TPS theoretical) - **High Latency** - Block confirmation times create delays (20+ seconds on Cardano) - **Increasing Fees** - Network congestion drives up transaction costs - **Resource Constraints** - Every node must process every transaction These limitations make certain applications impractical on Layer 1: - Real-time gaming - Micropayments - High-frequency trading - Interactive DApps ## How Hydra Solves This ### Isomorphic State Channels Hydra creates **isomorphic state channels** - off-chain environments that mirror Layer 1 capabilities: ```mermaid graph TD A["Cardano Layer 1
(Settlement Layer - Security)"] B["Hydra Head (Layer 2)
• High throughput
(1000+ TPS)
• Low latency
(<1 second)
• Minimal fees
• Full smart contract support"] A -- Open/Close Head --> B ``` ### Key Benefits #### 1. **Massive Throughput Increase** - **Layer 1**: \~250 TPS theoretical, \~50-100 TPS practical - **Hydra Head**: 1,000+ TPS per head - **Multiple Heads**: Linear scaling with number of heads ```typescript // Example: Processing multiple transactions rapidly for (let i = 0; i < 1000; i++) { await bridge.submitTx(tx) // Each transaction confirms in <1 second } ``` #### 2. **Near-Instant Finality** Transactions confirm in under 1 second within a Hydra Head: | Metric | Layer 1 | Hydra Head | | ----------------- | ----------- | ---------- | | Confirmation Time | 20+ seconds | <1 second | | Block Time | 20 seconds | \~100ms | | Finality | 2-3 minutes | Instant | #### 3. **Minimal Transaction Costs** - **Layer 1**: \~0.17 ADA average fee - **Hydra Head**: Negligible fees (only consensus overhead) - **Cost Savings**: 99%+ reduction for high-frequency operations ```typescript // Cost comparison const layer1Cost = 1000 * 0.17 // 170 ADA for 1000 txs const hydraCost = 2 * 0.17 + 0.001 * 1000 // ~1.4 ADA total // Savings: ~99.2% ``` #### 4. **Full Smart Contract Support** Hydra Heads support the same Plutus smart contracts as Layer 1: - ✅ Native tokens and NFTs - ✅ Custom validators - ✅ Complex DeFi protocols - ✅ State machines - ✅ Multi-signature logic #### 5. **Predictable Performance** Unlike Layer 1, Hydra Head performance is unaffected by: - Network congestion - Block size limits - Mempool competition - Other users' activities ## Use Cases ### 1. Gaming & Metaverse **Requirements**: Real-time interactions, frequent state updates, low costs ```typescript // In-game item transfer inside a Hydra Head — instant and feeless const transferItem = async (fromAddr, toAddr, itemNFT, utxos) => { const tx = await new TxBuilder({ isHydra: true }) .setInputs(utxos) .txOut(toAddr, [{ unit: `${itemNFT.policy}.${itemNFT.name}`, quantity: '1' }]) .changeAddress(fromAddr) .setFee('0') // no fee inside a Head .complete() const signed = await wallet.signTx(tx.to_hex()) await bridge.submitTxSync({ type: 'Witnessed Tx ConwayEra', description: '', cborHex: signed }) // Confirmed in <1 second } ``` **Examples**: - Player-to-player trading - Real-time leaderboards - In-game currency transactions - Tournament prize distribution ### 2. Micropayments & Streaming **Requirements**: Tiny transaction amounts, high frequency, minimal fees ```typescript // Per-second streaming payment inside a Hydra Head const streamingPayment = (creatorAddr, senderAddr, utxos) => { setInterval(async () => { const tx = await new TxBuilder({ isHydra: true }) .setInputs(utxos) .txOut(creatorAddr, [{ unit: 'lovelace', quantity: '1000' }]) // 0.001 ADA .changeAddress(senderAddr) .setFee('0') .complete() const signed = await wallet.signTx(tx.to_hex()) await bridge.submitTxSync({ type: 'Witnessed Tx ConwayEra', description: '', cborHex: signed }) }, 1000) // pay every second } ``` **Examples**: - Pay-per-second content streaming - Micropayment tips - Usage-based API payments - IoT device settlements ### 3. DeFi Trading **Requirements**: Fast execution, low latency, MEV resistance ```typescript // High-frequency DEX trade: spend the pool script UTxO, swap params in the redeemer const executeTrade = async (poolUtxo, swapRedeemer, dexScript, out, utxos) => { const tx = await new TxBuilder({ isHydra: true }) .setInputs(utxos) .txIn(poolUtxo.txHash, poolUtxo.outputIndex) .txInScript(dexScript) .txInRedeemerValue(swapRedeemer) .txOut(out.address, out.amount) .changeAddress(out.address) .setFee('0') .complete() const signed = await wallet.signTx(tx.to_hex()) await bridge.submitTxSync({ type: 'Witnessed Tx ConwayEra', description: '', cborHex: signed }) // Trade executes in <1 second } ``` **Examples**: - DEX order books - Automated market makers - Arbitrage opportunities - Liquidation mechanisms ### 4. NFT Marketplaces **Requirements**: Fast auctions, batch operations, low listing costs ```typescript // Real-time NFT auction: lock the bid at the auction script with a bid datum const placeBid = async (auctionScriptAddr, bidLovelace, bidDatum, bidderAddr, utxos) => { const tx = await new TxBuilder({ isHydra: true }) .setInputs(utxos) .txOut(auctionScriptAddr, [{ unit: 'lovelace', quantity: bidLovelace }]) .txOutInlineDatumValue(bidDatum) // records bidder + amount .changeAddress(bidderAddr) .setFee('0') .complete() const signed = await wallet.signTx(tx.to_hex()) await bridge.submitTxSync({ type: 'Witnessed Tx ConwayEra', description: '', cborHex: signed }) // Bid placed instantly } ``` **Examples**: - Live NFT auctions - Batch minting collections - Rapid trading - Royalty distributions ### 5. Social & Governance **Requirements**: High user interaction, voting, content moderation ```typescript // Real-time governance voting, recorded as transaction metadata const vote = async (proposalId, choice, voterAddr, utxos) => { const tx = await new TxBuilder({ isHydra: true }) .setInputs(utxos) .txOut(voterAddr, [{ unit: 'lovelace', quantity: '1000000' }]) .metadataValue(1, { proposal: proposalId, vote: choice }) .changeAddress(voterAddr) .setFee('0') .complete() const signed = await wallet.signTx(tx.to_hex()) await bridge.submitTxSync({ type: 'Witnessed Tx ConwayEra', description: '', cborHex: signed }) // Vote recorded instantly } ``` **Examples**: - DAO voting - Social media interactions - Content tipping - Reputation systems ## Comparing Hydra with Other Solutions ### Hydra vs Traditional Layer 1 | Feature | Cardano L1 | Hydra Head | | --------------- | ----------- | --------------------- | | Throughput | \~100 TPS | 1,000+ TPS | | Latency | 20+ seconds | <1 second | | Fees | \~0.17 ADA | \~0.001 ADA | | Smart Contracts | Full Plutus | Full Plutus | | Security | Layer 1 | Participant consensus | | Finality | 2-3 minutes | Instant | ### Hydra vs Other L2 Solutions **Hydra Advantages**: - ✅ Full smart contract support (isomorphic) - ✅ No separate virtual machine - ✅ Native multi-asset support - ✅ Instant finality within head - ✅ Predictable performance **Trade-offs**: - ⚠️ Requires participant cooperation - ⚠️ Limited to participants in head - ⚠️ Opening/closing has Layer 1 costs ## When to Use Hydra ### ✅ Ideal Use Cases - **High Transaction Volume**: Many transactions between known parties - **Low Latency Requirements**: Real-time or near-real-time interactions - **Cost Sensitivity**: Fees are a significant factor - **Known Participants**: Trusted or semi-trusted parties - **Session-Based**: Temporary, bounded interactions ### ❌ Not Ideal For - **One-Time Transactions**: Single, isolated transactions - **Unknown Parties**: No relationship or trust - **Long-Term Storage**: Permanent state without interaction - **Public Access**: Unrestricted open participation ## Getting Started Ready to integrate Hydra into your application? 1. **[Set up Hydra SDK](https://hydrasdk.com/getting-started/installation)** - Install required packages 2. **[Follow Integration Guide](https://hydrasdk.com/guides/hydra-integration)** - Step-by-step integration 3. **[Explore Commit/Decommit](https://hydrasdk.com/concepts/commit-to-hydra)** - Manage UTxOs in heads 4. **[Build Transactions](https://hydrasdk.com/concepts/transactions-in-hydra)** - Create Hydra transactions ## Learn More - [Hydra Official Documentation](https://hydra.family/head-protocol/){rel=""nofollow""} - [Hydra Research Papers](https://iohk.io/en/research/library/){rel=""nofollow""} - [Cardano Scaling Solutions](https://docs.cardano.org/scaling-solutions/){rel=""nofollow""} --- > **Next**: Learn how to [Commit UTxOs to Hydra](https://hydrasdk.com/concepts/commit-to-hydra) to start using Hydra Heads # Commit to Hydra Committing UTxOs into a Hydra Head moves assets from Cardano Layer 1 into a state channel (the Hydra Head). This unlocks fast, low-cost transactions inside the head. ::note This describes the **v1.x commit-based** head lifecycle. `@hydra-sdk/bridge` also supports Hydra **V2** (now stable, latest `2.2.0`), which removes the commit phase entirely — heads open directly and funds are added via incremental `deposit`s. See [Hydra Protocol Versions](https://hydrasdk.com/concepts/hydra-v2-changes) for how the two differ. :: ## Understanding commit ### What is a commit? A commit locks UTxOs on Layer 1 and makes them available inside a Hydra Head. Think of it as “depositing” into a fast lane for your transactions. ```text ┌─────────────────────────────────────────┐ │ Cardano Layer 1 │ │ │ │ Your UTxOs: [UTxO1, UTxO2, UTxO3] │ │ │ └──────────────┬──────────────────────────┘ │ │ COMMIT │ (locked on L1, available in head) │ ┌──────────────▼──────────────────────────┐ │ Hydra Head (Layer 2) │ │ │ │ Available: [UTxO1, UTxO2, UTxO3] │ │ Ready for fast transactions │ │ │ └─────────────────────────────────────────┘ ``` ### Key concepts - Initialization phase: Hydra Head must be in the `Initializing` state - Commit window: Time window to commit UTxOs before the head opens - Participant commits: Each participant commits their own UTxOs - Immutable after open: No further commits once the head is open (unless using incremental commit) - Layer 1 transaction: A commit is always an on-chain transaction ## Environment setup ### Requirements - Node.js 18+ - pnpm (or npm) - A running Hydra node - API {rel=""nofollow""} (HTTP) and ws\://localhost:10005 (WebSocket) - Blockfrost API key (preprod): [Register here](https://blockfrost.io/){rel=""nofollow""} - Testnet ADA faucet: [Get testnet ADA](https://docs.cardano.org/cardano-testnets/tools/faucet){rel=""nofollow""} - Guides: - [Run a local Hydra Node](https://hydra.family/head-protocol/docs/getting-started#start-hydra-nodes){rel=""nofollow""} - Or orchestrate multiple Hydra Nodes via our [Hydra Hexcore](https://alpha-v1.hexcore.io.vn/){rel=""nofollow""} ![Successfully connected to Hydra Node](https://hydrasdk.com/images/hydra-concept/commit-to-hydra/node-connected.png) ### Folder structure ```plaintext nodejs-app/ ├── src/ │ ├── common.ts # Shared config and API wrapper │ ├── cardano-query-utxo.ts # Query UTxOs from Cardano Layer 1 │ ├── empty-commit.ts # Empty commit sample (to open head) │ ├── partial-commit.ts # Commit with UTxO (incremental commit) │ └── ... # Other scripts ├── .env # Env config (Blockfrost key) └── package.json # Node.js project config ``` ### Common config and API wrapper `package.json` ```json { "name": "nodejs-app", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "start": "tsx src/index.ts" }, "dependencies": { "@hydra-sdk/cardano-wasm": "^1.0.0", "@hydra-sdk/core": "^1.4.1", "@hydra-sdk/transaction": "^1.2.0", "@hydra-sdk/bridge": "^1.3.2", "axios": "^1.4.0", "bignumber.js": "^9.1.1", "dotenv": "^16.3.1" }, "devDependencies": { "tsx": "^3.12.7", "typescript": "^5.2.2" } } ``` Install dependencies: ```bash pnpm install # or npm install ``` `.env` ```plaintext BLOCKFROST_PROVIDER_API_KEY=your_blockfrost_api_key_here ``` `src/common.ts` ```typescript import { AppWallet, Converter, NETWORK_ID, ProviderUtils, UTxO, UTxOObject } from '@hydra-sdk/core' import axios from 'axios' // Blockfrost provider (preprod) const blockfrostProvider = new ProviderUtils.BlockfrostProvider({ apiKey: process.env.BLOCKFROST_PROVIDER_API_KEY || '', network: 'preprod' }) // Wallet (test wallet) export const wallet = new AppWallet({ key: { type: 'mnemonic', words: 'your test mnemonic words here ...'.split(' ') }, networkId: NETWORK_ID.PREPROD, fetcher: blockfrostProvider.fetcher, submitter: blockfrostProvider.submitter }) export const walletAddress = wallet.getAccount().baseAddressBech32 // Hydra endpoints export const hydraConfig = { httpUrl: 'http://localhost:10005', wsUrl: 'ws://localhost:10005' } export type DepositToken = [string, Record] // Minimal API wrapper export class HydraApi { static instance = axios.create({ baseURL: hydraConfig.httpUrl, headers: { 'Content-Type': 'application/json' } }) static async queryAddressUTxO(address: string): Promise { try { const utxos = await this.instance.get('/snapshot/utxo') const utxoObj = utxos.data as Record return Converter.convertUTxOObjectToUTxO(utxoObj).filter( u => u.output.address === address ) } catch (error) { console.error('Error querying address UTxO:', error) return [] } } static async partialDeposit( blueprintTxCbor: string, utxo: UTxOObject, changeAddress: string ) { try { const response = await this.instance.post('/commit', { blueprintTx: { cborHex: blueprintTxCbor, type: 'Tx ConwayEra', description: 'Partial commit from NodeJS Playground' }, utxo, changeAddress }) return response.data as { cborHex: string description: string txId: string type: 'Tx ConwayEra' } } catch (error) { console.error('Error during partial deposit:', error) throw error } } static async commit(utxo: UTxOObject) { try { const response = await this.instance.post('/commit', { ...utxo }) return response.data as { cborHex: string description: string txId: string type: 'Tx ConwayEra' } } catch (error) { console.error('Error during commit:', error) throw error } } static async submitCardanoTx(tx: { cborHex: string; description: string; type: string }) { try { // Submit the signed CBOR to the Cardano Layer 1 network via the Hydra node const response = await this.instance.post('/cardano-transaction', tx) return response.data } catch (error) { console.error('Error submitting Cardano tx:', error) throw error } } } ``` ```typescript import { CardanoWASM } from '@hydra-sdk/cardano-wasm' import { wallet } from './common' import { Converter, Deserializer, hexToString } from '@hydra-sdk/core' import BigNumber from 'bignumber.js' async function main(address?: string) { const utxos = await wallet.queryUTxOs( address || wallet.getAccount().baseAddressBech32 ) console.log( '>>> UTxOs:', JSON.stringify(Converter.convertUTxOToUTxOObject(utxos), null, 2) ) const totalLovelace = utxos.reduce( (a, b) => a + Number(b.output.amount.find(x => x.unit === 'lovelace')?.quantity || 0), 0 ) console.log( '>>> Total lovelace:', BigNumber(totalLovelace).toFormat(), 'lovelace', ' => ', (Number(totalLovelace) / 1_000_000).toFixed(6), 'ADA' ) const totalAssets = utxos.reduce( (acc, utxo) => { utxo.output.amount.forEach(a => { if (a.unit !== 'lovelace') { if (!acc[a.unit]) { acc[a.unit] = 0 } acc[a.unit] += Number(a.quantity) } }) return acc }, {} as Record ) console.log('>>> Total assets:', Object.keys(totalAssets).length) for (const [unit, quantity] of Object.entries(totalAssets)) { const { policyId, assetName } = Deserializer.deserializeAssetUnit(unit) console.log( ` - ${policyId}${assetName ? '.' + assetName : ''}: ${BigNumber(quantity).toFormat()} ${hexToString(assetName)}` ) } } if (process.argv[2] && typeof process.argv[2] === 'string') { const address = process.argv[2] try { CardanoWASM.Address.from_bech32(address) } catch (error) { console.error('>>> address is invalid:', error) process.exit(1) } } else { main() } ``` ### Quick setup (Windows PowerShell) ```powershell # 1) Configure Blockfrost key (preprod) $env:BLOCKFROST_PROVIDER_API_KEY = "your_blockfrost_key" # 2) Install dependencies (if not already) cd nodejs-app pnpm install # 3) Ensure Hydra node is running at http://localhost:10005 and ws://localhost:10005 # 4) Run sample scripts npx tsx .\src\empty-commit.ts npx tsx .\src\partial-commit.ts ``` ## Commit to start using a Hydra Head - Condition: Head is in the `Initializing` state - Action: Each participant commits so the head can open (empty commits are allowed) ### Example - empty commit `src/empty-commit.ts` ```typescript import { HydraApi, wallet } from './common' async function main() { // Empty commit: send commit request without specifying concrete UTxOs const commitTx = await HydraApi.commit({} as any) if (commitTx) { const signedTx = await wallet.signTx(commitTx.cborHex, true) console.log('Signed commit tx:', signedTx) // Submit the signed CBOR to the Hydra node (if needed) const result = await HydraApi.submitCardanoTx({ cborHex: signedTx, description: 'Empty commit from NodeJS Playground', type: 'Witnessed Tx ConwayEra' }) } } main() ``` ### Example - commit a specific UTxO 1. Query UTxOs from Cardano Layer 1 (if needed) `src/cardano-query-utxo.ts` ```bash npx tsx ./src/cardano-query-utxo.ts ``` ```json { "c2e3452de098d13ae536c3fb9df599d119631d618aaa2738522aeced2d2a1ac2#0": { "address": "addr_test1qpxsf0x8xypuhq5k408f9kh0meyy6jv2lxgqw2fefvjlte0u06dugtmxuhhw8hschdn4q59g64q5s9z42ax6qyg7ewsqt6e548", "datum": null, "datumhash": null, "inlineDatum": null, "inlineDatumRaw": null, "referenceScript": null, "value": { "lovelace": 1327480, "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed72": { "4d494e": 2000000000 }, "fef67460342d081cb7881318b1f33b87626d1a1042b4c2acbbc0725d": { "7441424f": 1000000 } } } } ``` 2. Commit with a specific UTxO `src/commit-with-utxo.ts` ```typescript import { Converter, UTxOObject } from '@hydra-sdk/core' import { HydraApi, wallet, walletAddress } from './common' async function main() { // 1) Prepare the specific UTxO to commit const utxoToCommit: UTxOObject = { 'c2e3452de098d13ae536c3fb9df599d119631d618aaa2738522aeced2d2a1ac2#0': { address: 'addr_test1qpxsf0x8xypuhq5k408f9kh0meyy6jv2lxgqw2fefvjlte0u06dugtmxuhhw8hschdn4q59g64q5s9z42ax6qyg7ewsqt6e548', datum: null, datumhash: null, inlineDatum: null, inlineDatumRaw: null, referenceScript: null, value: { lovelace: 1327480, e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed72: { '4d494e': 2000000000 }, fef67460342d081cb7881318b1f33b87626d1a1042b4c2acbbc0725d: { '7441424f': 1000000 } } } } // 2) Call commit with the specific UTxO const result = await HydraApi.commit(utxoToCommit) if (result) { const signedTx = await wallet.signTx(result.cborHex, true) console.log('Signed commit tx:', signedTx) // 3) Submit the signed CBOR to the Hydra node const submitResult = await HydraApi.submitCardanoTx({ cborHex: signedTx, description: 'Commit specific UTxO from NodeJS Playground', type: 'Witnessed Tx ConwayEra' }) } } main() ``` ```bash npx tsx ./src/commit-with-utxo.ts ``` ## Incremental commit (deposit more into an open head) - Condition: Head is in the `Open` state - Action: Any participant can commit additional UTxOs Deposit variants for an open head: - Provide UTxOs only: Simple deposit, the entire UTxO is deposited into the head - Provide UTxOs with a blueprint transaction without outputs: Entire UTxO is deposited, and you can attach a blueprint transaction (for dApps) - Provide UTxOs with a blueprint transaction that has outputs but no change address: Requires a fully balanced blueprint; all outputs are deposited - Provide UTxOs with a blueprint transaction that has outputs and a change address: hydra-node will balance the tx; any change is returned to the provided address, while outputs are deposited ### Example: Commit the entire UTxO You can use the same `HydraApi.commit()` method as shown in the initial commit examples above to commit entire UTxOs during the incremental deposit phase. ### Example: Commit with a blueprint transaction (no outputs) When committing without outputs in the blueprint transaction, the entire UTxO will be deposited. This is useful when you want to attach metadata or additional information via the blueprint transaction. ### Example: Commit with a blueprint transaction (has outputs, no change address) ::warning **Not recommended**: Requires a fully balanced blueprint; all outputs are fully deposited. This approach is complex and error-prone. Use the method with change address instead (shown below). :: ### Example: partial commit (with change address) ::tip **Recommended**: hydra-node balances the tx and sends change back to the provided changeAddress; outputs are deposited :: `src/partial-commit.ts` ```typescript import { Converter, Deserializer, Resolver, UTxO } from '@hydra-sdk/core' import { HydraApi, wallet, walletAddress } from './common' import { TxBuilder } from '@hydra-sdk/transaction' /** * Partial commit example * 1. Make sure you have some assets in your wallet * 2. Run this script to create a partial commit transaction * 3. Sign the transaction and submit it to Hydra node * * Note: Build the blueprint transaction * - https://hydra.iohk.io/docs/hydra-node/tutorials/blueprint-tx/ * - The outputs of the blueprint transaction will be used as inputs for the partial commit transaction * * Command: * > npx tsx src/hydra/partial-commit.ts */ async function main() { console.log('>>> walletAddress:', walletAddress) const l1UTxOs = await wallet.queryUTxOs(walletAddress) const depositLovelace = 180_000_000 // 180 ADA // assets to deposit const depositAssetUnits = [ { unit: 'e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed724d494e', quantity: '1000000' // 1,000,000 token units } ] // check if asset exists in the wallet const hasAsset = l1UTxOs.some(utxo => utxo.output.amount.some(a => depositAssetUnits.findIndex(b => b.unit === a.unit) >= 0) ) if (!hasAsset) { throw new Error(`No asset ${depositAssetUnits.join(', ')} found in the wallet`) } const txBuilder = new TxBuilder({ errorLogger: true, isHydra: true, params: { minFeeA: 0, minFeeB: 0 } }) // build the blueprint transaction const blueprintTx = await txBuilder .setInputs(l1UTxOs) .addOutput({ address: walletAddress, amount: [{ unit: 'lovelace', quantity: depositLovelace.toString() }, ...depositAssetUnits] }) .setFee('0') .complete() console.log('>>> blueprintTx.to_hex():', blueprintTx.to_hex()) const txHash = Resolver.resolveTxHash(blueprintTx.to_hex()) console.log('>>> txHash:', txHash) const txInputs = Deserializer.deserializeTx(blueprintTx.to_hex()).body().inputs() const utxoToCommit: UTxO[] = [] for (let i = 0; i < txInputs.len(); i++) { const input = txInputs.get(i) if (input) { const utxo = l1UTxOs.find( u => u.input.txHash === input.transaction_id().to_hex() && u.input.outputIndex === input.index() ) if (utxo) { utxoToCommit.push(utxo) } } } const partialCommitResult = await HydraApi.partialDeposit( blueprintTx.to_hex(), Converter.convertUTxOToUTxOObject(utxoToCommit), walletAddress ) if (partialCommitResult) { const signedTx = await wallet.signTx(partialCommitResult.cborHex, true) console.log('>>> Signed partial commit tx:', { ...partialCommitResult, cborHex: signedTx }) } } main() ``` Run the script: ```bash npx tsx ./src/partial-commit.ts ``` ## Commit a script UTxO into the head - Condition: Head is `Initializing` or `Open` - Action: A participant can commit UTxOs locked by a Plutus script > Example: To be added (work in progress) ## References - {rel=""nofollow""} - {rel=""nofollow""} - {rel=""nofollow""} # Decommit from Hydra Decommit is the process of withdrawing UTxOs from an active Hydra Head back to Cardano Layer 1. This lets you exit the Layer 2 environment and make your assets available on the main chain. ## Understanding decommit ### What is decommit? Decommit removes UTxOs from the Hydra Head and returns them to Layer 1 while the head remains open. Think of it as moving funds from the express lane back to the main road. ```text ┌─────────────────────────────────────────┐ │ Hydra Head (Layer 2) │ │ │ │ Your UTxOs: [UTxO1, UTxO2, UTxO3] │ │ │ └──────────────┬──────────────────────────┘ │ │ DECOMMIT │ (Remove from Head, return to L1) │ ┌──────────────▼──────────────────────────┐ │ Cardano Layer 1 │ │ │ │ Available: [UTxO1, UTxO2, UTxO3] │ │ Back on main chain │ │ │ └─────────────────────────────────────────┘ ``` ## Decommit process ### When should you decommit? You should decommit when you want to withdraw assets from a Hydra Head without closing the Head. For example: moving assets back to your Layer 1 wallet or withdrawing a portion to use on Cardano mainnet. ### Steps to decommit 1. Connect to the Hydra node via Hydra Bridge. 2. Query current UTxOs in the Hydra Head. 3. Build a decommit transaction with the UTxOs you want to withdraw. 4. Sign the transaction with your wallet. 5. Submit the decommit transaction to the Hydra node. 6. Monitor the snapshot status and confirm the UTxOs have returned to Layer 1. ```ts // nodejs-playground/src/hydra/decommit.ts import { TxBuilder } from '@hydra-sdk/transaction' import { HydraBridge } from '@hydra-sdk/bridge' import { wallet, walletAddress } from './common' import { Resolver } from '@hydra-sdk/core' async function main() { const bridge = new HydraBridge({ url: 'ws://localhost:4001' }) const connected = await bridge.connect() if (!connected) { throw new Error('Failed to connect to Hydra node') } console.log('>>> Connected to Hydra node') const utxoObj = await bridge.querySnapshotUtxo() const addrUtxos = await bridge.queryAddressUTxO(walletAddress) const txBuilder = new TxBuilder({ isHydra: true, params: { minFeeA: 0, minFeeB: 0 } }) const tx = await txBuilder .setInputs([ addrUtxos[0] // utxo to decommit ]) .addOutput({ address: walletAddress, amount: [{ unit: 'lovelace', quantity: String(2_000_000) }] }) .changeAddress(walletAddress) .complete() const signedCbor = await wallet.signTx(tx.to_hex()) const txId = Resolver.resolveTxHash(signedCbor) const rs = await bridge.decommit({ cborHex: signedCbor, txId, timeout: 30000 }) console.log('>>> Submit tx result:', rs) } main() ``` ## Run ```bash npx tsx src/hydra/decommit.ts ``` ```plaintext >>> Connected to Hydra node >>> Snapshot UTxO: { 'dee1097738688441b2bcf90d9a20ad8eca859375ffdb8f1fde0f78f461345435#0': { address: 'addr_test1qpxsf0x8xypuhq5k408f9kh0meyy6jv2lxgqw2fefvjlte0u06dugtmxuhhw8hschdn4q59g64q5s9z42ax6qyg7ewsqt6e548', datum: null, datumhash: null, inlineDatum: null, inlineDatumRaw: null, referenceScript: null, value: { lovelace: 2000000 } }, 'dee1097738688441b2bcf90d9a20ad8eca859375ffdb8f1fde0f78f461345435#1': { address: 'addr_test1qpxsf0x8xypuhq5k408f9kh0meyy6jv2lxgqw2fefvjlte0u06dugtmxuhhw8hschdn4q59g64q5s9z42ax6qyg7ewsqt6e548', datum: null, datumhash: null, inlineDatum: null, inlineDatumRaw: null, referenceScript: null, value: { lovelace: 16000000 } } } >>> Submit tx result: { decommitTxId: '7318b97f468ad3dcc91b2fab180995f1bd62b4c49f68d83b3f30b5d8bffc49e4', headId: '7489fdc412ff71a7831ed508e73b2872482099fd4d97ed73663c70f8', seq: 227169, tag: 'DecommitApproved', timestamp: '2025-11-26T08:22:34.822012138Z', utxoToDecommit: { '7318b97f468ad3dcc91b2fab180995f1bd62b4c49f68d83b3f30b5d8bffc49e4#0': { address: 'addr_test1qpxsf0x8xypuhq5k408f9kh0meyy6jv2lxgqw2fefvjlte0u06dugtmxuhhw8hschdn4q59g64q5s9z42ax6qyg7ewsqt6e548', datum: null, datumhash: null, inlineDatum: null, inlineDatumRaw: null, referenceScript: null, value: [Object] } } } ``` ### Decommit message received on Hydra Head WebSocket ```json { "distributedUTxO": { "e492f070c2f3449273a1dc6c98391992ea46c88cb2b47c38043926415fe9f8f7#0": { "address": "addr_test1qpxsf0x8xypuhq5k408f9kh0meyy6jv2lxgqw2fefvjlte0u06dugtmxuhhw8hschdn4q59g64q5s9z42ax6qyg7ewsqt6e548", "datum": null, "datumhash": null, "inlineDatum": null, "inlineDatumRaw": null, "referenceScript": null, "value": { "lovelace": 2000000 } } }, "headId": "7489fdc412ff71a7831ed508e73b2872482099fd4d97ed73663c70f8", "seq": 227171, "tag": "DecommitFinalized", "timestamp": "2025-11-26T08:23:07.390634932Z" } ``` ### Best practices - Only decommit when necessary to avoid unnecessary Layer 1 fees. - Carefully check UTxOs before decommitting to avoid asset loss due to mistakes. - Monitor the snapshot status after decommit to ensure UTxOs have returned to Layer 1. - If the Head is closed, all UTxOs will be returned to Layer 1 automatically (no manual decommit needed). ### References - [Commit to Hydra](https://hydrasdk.com/concepts/commit-to-hydra) - [Transactions in Hydra](https://hydrasdk.com/concepts/transactions-in-hydra) - [Hydra Head Protocol - Decommit Funds](https://hydra.family/head-protocol/docs/how-to/incremental-decommit){rel=""nofollow""} # Transactions in Hydra Transactions within a Hydra Head operate similarly to Layer 1 but execute much faster with minimal fees. This guide explains how to build, submit, and track transactions in Hydra. ## Understanding Hydra Transactions ### Key differences from Layer 1 | Aspect | Layer 1 | Hydra Head | | ----------------- | ----------- | ------------------------ | | Confirmation Time | 20+ seconds | <1 second | | Transaction Fee | \~0.17 ADA | Negligible | | Throughput | \~100 TPS | 1,000+ TPS | | Smart Contracts | Full Plutus | Full Plutus (isomorphic) | | Finality | 2-3 minutes | Instant | ### Transaction lifecycle in Hydra ```mermaid graph TD A[1. Build Transaction] --> B[2. Sign with Private Key] B --> C["3. Submit to Hydra Head (via WebSocket)"] C --> D["4. Snapshot Updated (<1s)"] D --> E[5. Transaction Confirmed] ``` ## Building and submitting transactions in Hydra ### 1. Prerequisites To submit a transaction to a Hydra Head, you must have a Head running and be connected to the Hydra node (via WebSocket in the NodeJS playground examples). ### 2. Check UTxO / assets in the Head ```ts // src/start-bridge.ts import { HydraBridge } from '@hydra-sdk/bridge' async function main() { const bridge = new HydraBridge({ url: 'ws://localhost:4001' }) const connected = await bridge.connect() if (!connected) { throw new Error('Failed to connect to Hydra node') } console.log('>>> Connected to Hydra node') const utxoObj = await bridge.querySnapshotUtxo() console.log('>>> Snapshot UTxO:', utxoObj) } main() ``` ```bash npx tsx src/start-bridge.ts ``` Example snapshot output: ```plaintext >>> Connected to Hydra node >>> Snapshot UTxO: { 'fa1e2b6eb32b555201dd42140802c49dd56a8093838f3976071281276b93369f#0': { address: 'addr_test1qpxsf0x8xypuhq5k408f9kh0meyy6jv2lxgqw2fefvjlte0u06dugtmxuhhw8hschdn4q59g64q5s9z42ax6qyg7ewsqt6e548', datum: null, datumhash: null, inlineDatum: null, inlineDatumRaw: null, referenceScript: null, value: { lovelace: 18000000 } } } ``` ### 3. Build transaction ```ts // src/build-tx.ts import { HydraBridge } from '@hydra-sdk/bridge' import { Resolver } from '@hydra-sdk/core' import { TxBuilder } from '@hydra-sdk/transaction' import { wallet, walletAddress } from './common' async function main() { const bridge = new HydraBridge({ url: 'ws://localhost:4001' }) const connected = await bridge.connect() if (!connected) { throw new Error('Failed to connect to Hydra node') } console.log('>>> Connected to Hydra node') const utxoObj = await bridge.querySnapshotUtxo() console.log('>>> Snapshot UTxO:', utxoObj) const addrUtxos = await bridge.queryAddressUTxO(walletAddress) const txBuilder = new TxBuilder({ isHydra: true, params: { minFeeA: 0, minFeeB: 0 } }) const tx = await txBuilder .setInputs(addrUtxos) .addOutput({ address: walletAddress, // destination address amount: [{ unit: 'lovelace', quantity: String(2_000_000) }] }) .changeAddress(walletAddress) .complete() const signedCbor = await wallet.signTx(tx.to_hex()) const txId = Resolver.resolveTxHash(signedCbor) const rs = await bridge.submitTxSync({ cborHex: signedCbor, type: 'Witnessed Tx ConwayEra', description: 'Test Hydra tx from NodeJS Playground', txId: txId }) console.log('>>> Submit tx result:', rs) } main() ``` ### Alternative: callback-style submission (v1.3.0+) `submitTx` is the non-blocking variant — useful when you don't want to `await` confirmation: ```typescript bridge.submitTx( signedTx, (error, result) => { if (error) { console.error('Rejected:', error.reason) return } console.log('Snapshot #', result!.result?.snapshot?.number) } ) ``` ### 4. Run the code ```bash npx tsx src/build-tx.ts ``` Example output: ```plaintext >>> Connected to Hydra node >>> Snapshot UTxO: { 'fa1e2b6eb32b555201dd42140802c49dd56a8093838f3976071281276b93369f#0': { address: 'addr_test1qpxsf0x8xypuhq5k408f9kh0meyy6jv2lxgqw2fefvjlte0u06dugtmxuhhw8hschdn4q59g64q5s9z42ax6qyg7ewsqt6e548', datum: null, datumhash: null, inlineDatum: null, inlineDatumRaw: null, referenceScript: null, value: { lovelace: 18000000 } } } >>> Submit tx result: { txId: 'dee1097738688441b2bcf90d9a20ad8eca859375ffdb8f1fde0f78f461345435', isValid: true, isConfirmed: true, result: { headId: '7489fdc412ff71a7831ed508e73b2872482099fd4d97ed73663c70f8', seq: 227108, signatures: { multiSignature: [Array] }, snapshot: { confirmed: [Array], headId: '7489fdc412ff71a7831ed508e73b2872482099fd4d97ed73663c70f8', number: 1, utxo: [Object], utxoToCommit: null, utxoToDecommit: null, version: 0 }, tag: 'SnapshotConfirmed', timestamp: '2025-11-26T08:01:02.238499906Z' } } ``` # Smart Contracts in Hydra Hydra Heads support **isomorphic** smart contracts — the same Plutus scripts that run on Layer 1 work identically in Layer 2. This enables complex DeFi protocols, NFT mechanisms, and DApp logic with instant finality and minimal fees. ## Isomorphic Smart Contracts ### What does isomorphic mean? **Isomorphic** means Hydra Heads use the same execution environment as Layer 1: - ✅ Same validator scripts - ✅ Same validation logic - ✅ Same datum/redeemer structure - ✅ Same native asset support - ✅ No code changes required ```text ┌──────────────────────────────┐ │ Your validator script │ │ │ │ validator :: Datum -> │ │ Redeemer -> │ │ ScriptContext ->│ │ Bool │ └──────────┬─────────┬─────────┘ │ │ ┌─────▼─────┐ │ │ Layer 1 │ │ │ Mainnet │ │ └───────────┘ │ │ ┌──────▼──────┐ │ Hydra Head │ │ (Layer 2) │ └─────────────┘ ``` ## Using Smart Contracts in Hydra Plutus smart contracts operate inside a Hydra Head the same way they operate on Cardano Layer 1. That means you can reuse the validator, datum, and redeemer without changing the on-chain logic while benefiting from fast transactions, low fees, and instant finality inside the Head. ### Quick process summary - Write your Layer 1 validator as usual — keep datum/redeemer formats consistent. - Lock a script UTxO on Layer 1 (for example, commit it to a Head or reference an existing script UTxO). - In the Hydra Head, build an (off-chain) transaction that uses the same script, datum, and redeemer. - The transaction is validated by Head participants against the validator rules; if valid, the Head’s state updates. - When the Head is closed/settled, the final state is posted to L1 and the changes are applied by the script validator. ### Example: validator (Aiken, Always True) ```sh # always-true.ak (apps/nodejs-playground/src/contract/always-true.ak) use cardano/transaction.{ OutputReference, Transaction} validator always_true { spend ( _datum: Option, _redeemer: Data, _output_ref: OutputReference, _tx: Transaction, ) { True } else(_) { fail } } ``` After compilation you will get the file: `always-true.json`: [nodejs-playground/src/contract/always-true.json](https://github.com/Vtechcom/hydra-sdk/blob/master/apps/nodejs-playground/src/contract/always-true.json){rel=""nofollow""} ### Example: using the SDK (TypeScript) The sample scripts in `apps/nodejs-playground/src/contract/` demonstrate lock/unlock flows: - **Lock**: create a script UTxO → `npx tsx src/contract/hydra-lock.ts` ```ts import contract from './always-true.json' import { DatumUtils, Deserializer, TimeUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core' import { buildRedeemer, emptyRedeemer, TxBuilder } from '@hydra-sdk/transaction' // Build datum const { paymentCredentialHash: pubKeyHash } = Deserializer.deserializeAddress(walletAddress) const system_unlocked_at = Date.now() + 1 * 60 * 1000 // now + 1 minute const datum = DatumUtils.mkConstr(0, [ DatumUtils.mkBytes(pubKeyHash!), DatumUtils.mkInt(system_unlocked_at), ]) const txBuilder = new TxBuilder({ isHydra: true, params: { minFeeA: 0, minFeeB: 0 } }) const txLock = await txBuilder .setInputs(addressUTxO) .addOutput({ address: contract.address, amount: [{ unit: 'lovelace', quantity: String(2_000_000) }] }) .txOutInlineDatumValue(datum) .changeAddress(walletAddress) .complete() ``` - **Unlock**: consume the script UTxO → `npx tsx src/contract/hydra-unlock.ts ` ```ts import contract from './always-true.json' import { DatumUtils, Deserializer, TimeUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core' import { buildRedeemer, emptyRedeemer, TxBuilder } from '@hydra-sdk/transaction' const txBuilder = new TxBuilder({ isHydra: true, params: { minFeeA: 0, minFeeB: 0 } }) const SLOT_CONFIG: (typeof SLOT_CONFIG_NETWORK)['PREPROD'] = { zeroTime: 1762267312000, // Timestamp in ms when start hydra head network zeroSlot: 0, slotLength: 1000, epochLength: 432000, startEpoch: 0, } as const; const txUnlock = await txBuilder .setInputs(inputUTxOs) .txIn( scriptUTxO.input.txHash, scriptUTxO.input.outputIndex, scriptUTxO.output.amount, scriptUTxO.output.address ) .txInScript(contract.cborHex) .txInInlineDatum(scriptUTxO.output.inlineDatum!) .txInRedeemerValue( emptyRedeemer({ type: 'int', exUnits: { mem: '100000', steps: '25000000' } }) ) .txInCollateral( collateralUTxO.input.txHash, collateralUTxO.input.outputIndex, collateralUTxO.output.amount, collateralUTxO.output.address ) .addOutput({ address: walletAddress, amount: scriptUTxO.output.amount // send all assets back to myself }) .changeAddress(walletAddress) .invalidBefore(TimeUtils.unixTimeToEnclosingSlot(Date.now(), SLOT_CONFIG)) // must be after current slot .invalidAfter(TimeUtils.unixTimeToEnclosingSlot(Date.now() + 60 * 60 * 1000, SLOT_CONFIG)) // must be within 1 hour .complete() ``` ### Interaction flow (Mermaid) ```mermaid flowchart TD A[Participant A] -->|Build & Sign tx| B[Hydra Head] C[Participant B/C] -->|Validate & Sign| B B -->|Plutus Validator| D{Valid?} D -->|✅| E[Update Head State] D -->|❌| F[Reject Tx] E -->|Close/Contest| G[Settle L1] ``` ### Notes & Best Practices - Collateral: Consuming scripts on L1 still requires collateral; within a Head, the bonding/collateral rules depend on how you build the transaction bundle and the Head’s security policy. - Consistency: Always serialize datum/redeemer in the same format between L1 and Head. - Off-chain code: Off-chain logic (backend or dApp) is responsible for coordinating signatures, communicating with the Hydra node, and attaching the correct datum/redeemer values. - Testing: Validate your validator on an L1 testnet and test multi-participant Head scenarios to ensure identical behavior. - Minting/Burning: Mint/burn flows that use Plutus minting policies will still work in a Head if the policy doesn't depend on L1-specific data or time; keep policy semantics compatible. ### Limitations & Warnings - Opening/closing a Head is still an L1 transaction — settlement costs and L1 fees still apply for those actions. - Some validators that depend on L1-specific data (e.g., temporal constraints or slot-based checks) may need special handling when executed in a Head — verify semantics in context. - Data availability: Ensure participants maintain necessary off-chain data to reconstruct state for dispute or close flows. ### References - [Commit to Hydra](https://hydrasdk.com/concepts/commit-to-hydra) - [Transactions in Hydra](https://hydrasdk.com/concepts/transactions-in-hydra) - [Hydra Head Protocol](https://hydra.family/head-protocol/){rel=""nofollow""} - [Hydra Known Issues](https://hydra.family/head-protocol/docs/known-issues){rel=""nofollow""} # Hydra Protocol Versions Hydra SDK talks to a `hydra-node`, and the node follows the Hydra Head protocol's own release cycle. This page tracks that cycle and which node versions the SDK supports. ::note `@hydra-sdk/bridge` **2.x targets the hydra-node v2 line** (verified against **2.3.0**). The bridge major version tracks the hydra-node major version. Bridge **1.x is end of life** — it spoke the v1 **commit-based** flow and receives no further releases. v2 removed the commit phase entirely (ADR-33): a head opens directly, and funds enter through **incremental deposits**. Upgrading is a breaking change; see the [migration guide](https://hydrasdk.com/resources/migration). :: ## Version timeline | Version | Date | Line | Status | Theme | | ------- | ---------- | ---- | ----------------------- | ------------------------------------------------------- | | `1.0.0` | 2025-10-08 | v1 | Stable | First production-ready release | | `1.1.0` | 2025-10-28 | v1 | Stable | Deposit & fanout improvements | | `1.2.0` | 2025-11-28 | v1 | Stable | `SafeClose`, Blockfrost TUI | | `1.3.0` | 2026-03-05 | v1 | **Stable (SDK target)** | Sync, rollback & fee optimization | | `1.2.1` | 2026-04-22 | v1 | Pre-release | Backport: era-aware `EpochInfo` fix | | `2.0.0` | 2026-04-02 | v2 | Alpha (initial V2) | Protocol simplification (commit-less) | | `2.1.0` | 2026-05-13 | v2 | Superseded by 2.2.0 | SQLite persistence, snapshot & security | | `2.2.0` | 2026-06-12 | v2 | Superseded by 2.3.0 | Partial fanout, cheaper L2 transactions | | `2.3.0` | 2026-07-15 | v2 | **Stable (SDK target)** | No snapshot Plutus re-eval, HD-wallet keys, YAML config | ## v1.3.0 — Stable v1 line `1.3.0` is the largest and most stable release in the v1 line, and is fully supported by `@hydra-sdk/bridge`. Highlights relevant to SDK integrations: - **\~4× cheaper fees** for `init` / `open` and related on-chain transactions, thanks to improved fee estimation. - **Out-of-sync protection (breaking)** — the node now rejects inputs while it is behind the chain, surfacing `NodeUnsynced` / `NodeSynced` state. Client code should wait for `NodeSynced` before submitting. - **Richer `Greetings` (breaking)** — the greeting message now carries `currentSlot` and sync status; `@hydra-sdk/bridge` reads these extended fields. - **Longer contestation period (breaking)** — `defaultContestationPeriod` changed from 10 minutes to **12 hours**, which affects how long closing a head takes. - **Bounded transactions per snapshot** and fixes for deposits/decommits across chain rollbacks. **Compatible tooling:** `cardano-node 10.6.2` · `cardano-cli 10.15.0.0` · `mithril 2524.0` ::note `v1.2.1` (pre-release) backports the era-aware `EpochInfo` fix from V2 to the v1 line, correcting `POSIXTime` values inside Plutus `ScriptContext` on multi-era chains. It is a pre-release — prefer `1.3.0`. :: --- Everything below describes the **V2 line**. It changes the head lifecycle and on-chain scripts; V2 stabilized with `2.2.0`. ## 2.0.0 — Protocol simplification ### Remove commit phase — directly open heads The most fundamental change in Hydra V2: **the commit phase is removed**. In V1, opening a head required all participants to commit UTxOs during an initialization phase (`Init` → `Commit` → `CollectCom`). In V2: - Heads open **directly** after initialization — no `commit`, `collectCom`, or `abort` transactions. - Funds are added **incrementally** via `deposit` transactions while the head is already open. - This decision is documented in [ADR-33](https://hydra.family/head-protocol/adr/33){rel=""nofollow""}. **Benefits:** - **Simplified lifecycle**: removes the multi-step head opening process, reducing operational complexity. - **Resolved "non-abortable head" issue**: previously, a participant committing too large a UTxO could make the head un-abortable. This problem no longer exists. - **Lower costs**: eliminating `collectCom` and `abort` transactions reduces total on-chain costs for most use cases. See [transaction cost benchmarks](https://hydra.family/head-protocol/unstable/benchmarks/transaction-cost){rel=""nofollow""}. **API changes:** - `/commit` endpoint behavior changes — it now accepts `deposit` transactions directly since heads open without a prior commit phase. - Head initialization endpoint (`GET /head-initialization`) removed. - All on-chain scripts changed — new script hashes across all networks. ### HydraHeadV2 token The head token name has been upgraded from `HydraHeadV1` to `HydraHeadV2`. This ensures version differentiation and prevents interaction between V1 and V2 heads — code that identifies heads on L1 by token name must be updated. ### Era-aware Plutus scripts L2 ledger `Globals` now uses era-aware `EpochInfo` queried from the blockchain instead of `fixedEpochInfo`, ensuring correct `POSIXTime` values for time-sensitive Plutus scripts on multi-era chains (mainnet/testnet). ### Snapshot & deposit fixes | Issue | Description | | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Stuck in RequestedSnapshot** | Fixed a head getting permanently stuck when `CommitFinalized` races with an in-flight `ReqSn`. Only `SeenSnapshot` now blocks re-request; `RequestedSnapshot` retries correctly. | | **Silently dropped deposits** | Deposits that become active while a snapshot is in-flight are no longer silently dropped — the next chained snapshot picks them up via `selectNextDeposit`. | | **Cross-head deposit contamination** | Deposits from other heads are no longer picked up when multiple heads share the same network — `depositsForHead` is applied consistently, and deposit aggregate cases are guarded by `headId`. | | **100 ADA commit limit removed** | The hard-coded 100 ADA mainnet commit limit was removed. Until `2.2.0`, conservative deposit amounts were still advised for full fund safety — the **Partial fanout** feature in `2.2.0` (below) lifts that ceiling entirely. | ## 2.1.0 — Persistence & resilience ::note `2.1.0` is superseded by `2.2.0` — prefer `2.2.0` for the V2 line. :: ### SQLite-backed event store File-based persistence (the append-only JSON `state` file) is replaced with a **SQLite-backed event store** (`hydra.db`): - Events are persisted in `/hydra.db`. - On first startup after upgrading, existing `state` files are **automatically migrated** into `hydra.db` and renamed to `state.migrated`. - Startup and recovery performance improve significantly. ### Deposit security hardening The deposit Plutus validator has been hardened against **malformed increment transactions**, closing a security gap in on-chain deposit handling. ### Snapshot latency improvement Snapshot confirmation latency is reduced by **\~7% on average** by caching pre-computed signable bytes in `SeenSnapshot`. This avoids repeated UTxO serialization and hashing on every `AckSn` verification during signing rounds. ### Blockfrost error resilience Transient chain-following errors in the Blockfrost backend (`DecodeError`, `MissingNextBlockHash`, etc.) are now retried with **exponential backoff** instead of crashing the node. ### Other changes - `List` replaced with `Seq` to speed up transaction processing in certain operations. - Several `StateChanged` event fields were renamed or removed (see breaking changes below). - Upgraded to `cardano-node` protocol version 12+. **Compatible tooling:** `cardano-node 11.0.1` · `cardano-cli 11.0.0` · `mithril 2617` ## 2.2.0 — Partial fanout & cheaper L2 This is the current stable V2 release. ### Partial fanout — no more per-head UTxO ceiling Until now, the number of UTxOs a head could hold was effectively capped by what fit in a single fanout transaction. **Partial fanout** removes that limit: - A head with an arbitrarily large UTxO set is closed out across multiple steps — each `PartialFanoutTx` distributes as many outputs as fit in one transaction, and a final `FinalPartialFanoutTx` burns the head tokens to complete the process. - Every step is verified on-chain with a **BLS accumulator membership proof**. - The chunk size is determined **dynamically** (binary search over valid sizes) rather than by a hard-coded limit. This resolves the conservative-deposit caveat from V2's early releases. ### Cheaper L2 transactions `utxoCostPerByte` is set to **zero** on L2, so a `NewTx` can be submitted with as little as **1 lovelace** — lowering the cost of transacting inside a head. ### Revamped hydra-tui A substantially revised `hydra-tui`: pending-deposit recovery from `Open` and `Closed`/`Final` states, a dark/light theme toggle persisted to config, an event-history filter (all vs errors only), and tab navigation. ### Correctness & performance fixes - Fixed replaying persisted events from a previous head corrupting the state of a newly opened head. - Fixed a recovered incremental-commit deposit reappearing in the L2 UTxO after sideloading a snapshot (which made the same UTxO spendable on L1 and L2 at once). - Added a `mustNotMintOrBurn` guard to the `Increment` and `Decrement` validator transitions. - Reduced on-disk event-store growth by dropping redundant `newLocalUTxO` fields (post-tx UTxO is recomputed arithmetically). - Lazy `Map` replaced with **strict `Map`** to improve memory usage. **Compatible tooling:** `cardano-node 11.0.1` · `cardano-cli 11.0.0` · `mithril 2617` ## Breaking changes summary (V2) ### 2.1.0 — StateChanged event schema | Event | Change | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `SnapshotRequested` | `snapshot` → `requestedSnapshot`; `requestedTxIds` removed (now carried inside the snapshot) | | `PartySignedSnapshot` | `snapshot :: Snapshot tx` → `snapshotNumber :: SnapshotNumber` | | `SnapshotConfirmed` | `snapshot :: Snapshot tx` → `snapshot :: Maybe (Snapshot tx)` (`Nothing` when the snapshot was already carried by `SnapshotRequested`) | | `DecommitRecorded` | `utxoToDecommit :: UTxOType tx` field removed | ### 2.2.0 — Fanout & UTxO fields | Field / event | Change | | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `HeadIsFinalized` (server output) | `utxo` → `finalizedUTxO`; type changed from a UTxO map (keyed by `TxIn`) to an **array of `TxOut`** (intermediate partial-fanout outputs carry no spending reference) | | `ClosedState` (persisted) | `remainingFanoutUTxO` → `remainingFanoutOutputs`, `distributedFanoutUTxO` → `distributedFanoutOutputs`; both UTxO map → **array of `TxOut`** | | `TransactionAppliedToLocalUTxO`, `SnapshotRequested`, `DecommitRecorded` | `newLocalUTxO` field removed | ### Hydra script hashes On-chain scripts have new hashes with each protocol change. For the **v1.3.0** and **V2** lines (both supported by the bridge): | Network | 1.3.0 | 2.1.0 | 2.2.0 (two scripts) | | --------- | -------------------- | -------------------- | ------------------------------------------ | | `preview` | `8ae405c2…cf88621` | `86288ee0…add66c4` | `52c84ab1…e799ad6c`, `dd4b3920…afb95f41` | | `preprod` | `476b37a7…a11e8f3da` | `16f275a5…54298e46` | `2c01bf78…9ea00b03`, `395ecc51…af4696dd` | | `mainnet` | `2c6cff50…4d5e8b67` | `a864514b…c45539da2` | `17fb77e1…b29d276b9`, `906ecc6e…76dbfc4a3` | `2.2.0` publishes **two** script hashes per network because partial fanout adds the accumulator script. Always confirm the full hashes against the [networks.json](https://github.com/cardano-scaling/hydra/blob/master/hydra-node/networks.json){rel=""nofollow""} for your exact node version. ## Migrating a node from V1 to V2 V1 → V2 is a **breaking, one-way change** at the protocol level — different protocol, token, and scripts, with no backward compatibility between a V1 and a V2 node. `@hydra-sdk/bridge` supports both lines, so no SDK change is required; when you switch a node from V1 to V2: 1. **Close all active heads** before upgrading the node. 2. **Update script references** — all hydra script hashes changed; update your network/script configuration. 3. **Update API consumers** — adjust client code for the new `StateChanged` event schema, the `HeadIsFinalized`/`ClosedState` field changes, and the removed head-initialization endpoint. 4. **Adapt the deposit workflow** — replace the commit-then-open pattern with direct-open-then-deposit. 5. **Verify the persistence directory** — existing `state` files are auto-migrated to SQLite on first startup. 6. **Upgrade companion tools** — `cardano-node >= 11.0.1`, `cardano-cli >= 11.0.0`, `mithril >= 2617`. ## References - [ADR-33 — Directly Open Heads](https://hydra.family/head-protocol/adr/33){rel=""nofollow""} - [Transaction Cost Benchmarks](https://hydra.family/head-protocol/unstable/benchmarks/transaction-cost){rel=""nofollow""} - [GitHub Releases](https://github.com/cardano-scaling/hydra/releases){rel=""nofollow""} - [Full Changelog](https://github.com/cardano-scaling/hydra/blob/master/CHANGELOG.md){rel=""nofollow""} - [Hydra Head Protocol Documentation](https://hydra.family/head-protocol/){rel=""nofollow""} # API Reference The Hydra SDK is four packages plus a comprehensive utilities collection. Each page below documents the constructors, methods, parameters, and return types for one package. ::card-group :::card --- icon: i-lucide-box title: "@hydra-sdk/core" to: https://hydrasdk.com/api/core --- HD wallet management, signing, network integration, and Cardano operations. ::: :::card --- icon: i-lucide-cable title: "@hydra-sdk/bridge" to: https://hydrasdk.com/api/bridge --- Hydra Head lifecycle, WebSocket events, and transaction processing. ::: :::card --- icon: i-lucide-file-signature title: "@hydra-sdk/transaction" to: https://hydrasdk.com/api/transaction --- TxBuilder for Plutus, multi-asset, staking, and Hydra transactions. ::: :::card --- icon: i-lucide-cpu title: "@hydra-sdk/cardano-wasm" to: https://hydrasdk.com/api/cardano-wasm --- Cardano WASM bindings — Value, Address, Tx, Script, metadata, hashing. ::: :::card --- icon: i-lucide-wrench title: Utilities to: https://hydrasdk.com/api/utilities --- Datums, redeemers, policies, metadata, time/slot, and providers. ::: :: ## Installation Install the packages you need. Always include `@hydra-sdk/cardano-wasm` — it is the WASM core every package imports through, and must be a direct dependency for your bundler to configure it. ::code-group ```bash [core] npm install @hydra-sdk/core @hydra-sdk/cardano-wasm ``` ```bash [bridge] npm install @hydra-sdk/bridge @hydra-sdk/cardano-wasm ``` ```bash [transaction] npm install @hydra-sdk/transaction @hydra-sdk/cardano-wasm ``` ```bash [all] npm install @hydra-sdk/core @hydra-sdk/bridge @hydra-sdk/transaction @hydra-sdk/cardano-wasm ``` :: ## Package notes A few things worth knowing before you dive into the per-package pages. ### core — NETWORK\_ID is a Record, not an enum `NETWORK_ID` is a `Record` with keys `'MAINNET' | 'PREPROD' | 'PREVIEW'` (there is no `TESTNET`): ```ts import { AppWallet, NETWORK_ID } from '@hydra-sdk/core' // NETWORK_ID = { MAINNET: 1, PREPROD: 0, PREVIEW: 0 } const wallet = new AppWallet({ networkId: NETWORK_ID.MAINNET, // 1 key: { type: 'mnemonic', words: AppWallet.brew() } }) ``` ### bridge — key features - **hydra-node v1.3.0 through V2** — supports the v1.3.0 line (extended `Greetings` fields, `/head` API, `slotZeroTimestamp`) and the stable V2 line (commit-less, incremental `deposit`s). - **O(1) balance & UTxO reads** — a pre-computed in-memory snapshot cache; no I/O per balance call. - **Dual submit API** — `submitTxSync` (Promise) and `submitTx` (error-first callback). - **Auto-reconnect** — configurable interval and max-attempts; safe cancellation on `disconnect()`. - **5 URL formats** — `ws://`, `wss://`, `http://`, `https://`, and gateway with `?X-Api-Key=`. ### cardano-wasm — bundler tips ::tip When running in the browser, add `vite-plugin-wasm` and `vite-plugin-top-level-await`, and exclude `@hydra-sdk/cardano-wasm` from `optimizeDeps`. See [Configuration](https://hydrasdk.com/getting-started/configuration) for full bundler setup. :: ## What's new Release notes and version-by-version changes live in the [Changelog](https://hydrasdk.com/resources/changelog). # @hydra-sdk/core ## Functions --- ## AppWallet ### **`AppWallet.brew(strength?: number) → string[]`** Generate a new random mnemonic phrase for a new Cardano wallet. **Parameters:** - `strength?` (number): Mnemonic strength (128 = 12 words, 160 = 15 words, 256 = 24 words). Default: 256 (24 words) **Example:** ```typescript import { AppWallet } from '@hydra-sdk/core' // Generate 24-word mnemonic (default) const mnemonic24 = AppWallet.brew(256) // Generate 12-word mnemonic const mnemonic12 = AppWallet.brew(128) // Generate 15-word mnemonic const mnemonic15 = AppWallet.brew(160) console.log('New mnemonic:', mnemonic24) // => ['abandon', 'abandon', ..., 'zone'] ``` **Returns:** `string[]`:br Array of mnemonic words --- ### **`AppWallet.getAccount(accountIndex?: number, keyIndex?: number) → Account`** Get account information with derived keys and addresses. **Parameters:** - `accountIndex?` (number): Account index (per BIP-44). Default: 0 - `keyIndex?` (number): Address index. Default: 0 **Example:** ```typescript import { AppWallet, NETWORK_ID, ProviderUtils } from '@hydra-sdk/core' const blockfrostProvider = new ProviderUtils.BlockfrostProvider({ apiKey: 'your-blockfrost-api-key', network: 'preprod' }) const wallet = new AppWallet({ networkId: NETWORK_ID.PREPROD, key: { type: 'mnemonic', words: ['word1', 'word2', ...] }, fetcher: blockfrostProvider.fetcher, submitter: blockfrostProvider.submitter }) // Get first account (account 0, address 0) const account = wallet.getAccount(0, 0) console.log({ // Address information baseAddressBech32: account.baseAddressBech32, enterpriseAddressBech32: account.enterpriseAddressBech32, rewardAddressBech32: account.rewardAddressBech32, // Key information paymentKey: account.paymentKey, stakeKey: account.stakeKey, // Indices accountIndex: account.accountIndex, addressIndex: account.addressIndex }) // Default: account 0, address 0 const defaultAccount = wallet.getAccount() ``` **Returns:** `Account` - `baseAddressBech32`: Base address (payment + staking) - `enterpriseAddressBech32`: Enterprise address (payment only) - `rewardAddressBech32`: Reward address (staking only) - `paymentKey`: Payment key - `stakeKey`: Staking key - `accountIndex`: Account index - `addressIndex`: Address index --- ### **`AppWallet.getPaymentAddress(accountIndex?: number, keyIndex?: number) → string`** Get the base payment address (including staking). **Parameters:** - `accountIndex?` (number): Account index. Default: 0 - `keyIndex?` (number): Address index. Default: 0 **Example:** ```typescript const baseAddr = wallet.getPaymentAddress(0, 0) console.log('Base address:', baseAddr) // => "addr_test1qz2fxv2umyhttkxyxp8x0..." ``` **Returns:** `string`:br Base address in bech32 format --- ### **`AppWallet.getEnterpriseAddress(accountIndex?: number, keyIndex?: number) → string`** Get the enterprise address (payment only, no staking). **Parameters:** - `accountIndex?` (number): Account index. Default: 0 - `keyIndex?` (number): Address index. Default: 0 **Example:** ```typescript const enterpriseAddr = wallet.getEnterpriseAddress(0, 0) console.log('Enterprise address:', enterpriseAddr) // => "addr_test1vz2fxv2umyhttkxyxp8x0..." ``` **Returns:** `string`:br Enterprise address in bech32 format --- ### **`AppWallet.getRewardAddress(accountIndex?: number, keyIndex?: number) → string`** Get the reward address (staking only). **Parameters:** - `accountIndex?` (number): Account index. Default: 0 - `keyIndex?` (number): Address index. Default: 0 **Example:** ```typescript const rewardAddr = wallet.getRewardAddress(0, 0) console.log('Reward address:', rewardAddr) // => "stake_test1uqz2fxv2umyhttkxyxp8x0..." ``` **Returns:** `string`:br Reward address in bech32 format --- ### **`AppWallet.getNetworkId() → number`** Get the current network ID of the wallet. **Example:** ```typescript const networkId = wallet.getNetworkId() console.log('Network ID:', networkId) // => 0 (Preprod testnet) or 1 (Mainnet) ``` **Returns:** `number` - `0`: Testnet (Preprod) - `1`: Mainnet --- ### **`AppWallet.signTx(txCbor: string, partialSign?: boolean, accountIndex?: number, keyIndex?: number) → Promise`** Sign a Cardano transaction. Supports full or partial signing (multi-sig). **Parameters:** - `txCbor` (string): Transaction in CBOR hex format - `partialSign?` (boolean): If true, perform partial signing. Default: false - `accountIndex?` (number): Account index. Default: 0 - `keyIndex?` (number): Address index. Default: 0 **Example:** ```typescript // Full signing const signedTx = await wallet.signTx(unsignedTxCbor, false, 0, 0) // Partial signing (multi-sig) const partialSigned = await wallet.signTx(unsignedTxCbor, true, 0, 0) console.log('Signed transaction:', signedTx) ``` **Returns:** `Promise`:br Signed transaction in CBOR hex format --- ### **`AppWallet.signTxs(txCbors: string[], partialSign?: boolean) → Promise`** Sign multiple transactions at once. **Parameters:** - `txCbors` (string [] ): Array of transactions in CBOR hex format - `partialSign?` (boolean): If true, perform partial signing. Default: false **Example:** ```typescript const signedTxs = await wallet.signTxs( [txCbor1, txCbor2, txCbor3], false ) console.log('Signed transactions:', signedTxs) ``` **Returns:** `Promise`:br Array of signed transactions in CBOR hex format --- ### **`AppWallet.signData(address: string, payload: string, accountIndex?: number, keyIndex?: number) → Promise<{ signature: string; key: string }>`** Sign arbitrary data according to CIP-8 (Cardano Improvement Proposal). > **Not implemented on `AppWallet`.** Calling `AppWallet.signData()` throws `[AppWallet] signData() is not implemented.` Only `EmbeddedWallet.signData()` actually signs data — it has the same signature and returns `{ signature, key }`. **Parameters:** - `address` (string): Cardano address (bech32) - `payload` (string): Data to sign - `accountIndex?` (number): Account index. Default: 0 - `keyIndex?` (number): Address index. Default: 0 **Example:** ```typescript import { EmbeddedWallet } from '@hydra-sdk/core' // AppWallet.signData() throws — use EmbeddedWallet.signData() instead const signature = await embeddedWallet.signData( 'addr_test1qz2fxv2umyhttkxyxp8x0...', 'Hello World', 0, 0 ) console.log('Data signature:', signature) // => { signature: '...', key: '...' } ``` **Returns:** `Promise<{ signature: string; key: string }>` - `signature`: Signature in hex format - `key`: Public key in hex format --- ### **`AppWallet.submitTx(signedTxCbor: string) → Promise`** Submit a signed transaction to the blockchain. **Parameters:** - `signedTxCbor` (string): Signed transaction in CBOR hex format **Example:** ```typescript const txHash = await wallet.submitTx(signedTxCbor) console.log('Transaction hash:', txHash) // => "a1b2c3d4e5f6..." ``` **Returns:** `Promise`:br Transaction hash --- ### **`AppWallet.queryUTxOs(address: string) → Promise`** Query UTxOs (Unspent Transaction Outputs) for an address. **Parameters:** - `address` (string): Cardano address (bech32) **Example:** ```typescript const address = wallet.getPaymentAddress() const utxos = await wallet.queryUTxOs(address) console.log('UTxOs:', utxos) // => [{ input: { txHash: '...', index: 0 }, output: { address: '...', amount: [...] } }, ...] ``` **Returns:** `Promise`:br Array of UTxOs for the address --- ## EmbeddedWallet Low-level wallet implementation used internally by AppWallet. ### **`EmbeddedWallet.generateMnemonic(strength?: number) → string[]`** Generate a mnemonic phrase. **Parameters:** - `strength?` (number): Mnemonic strength (128/160/256). Default: 256 **Example:** ```typescript import { EmbeddedWallet } from '@hydra-sdk/core' const mnemonic = EmbeddedWallet.generateMnemonic(256) console.log('Mnemonic:', mnemonic) // => ['abandon', 'abandon', ..., 'zone'] ``` **Returns:** `string[]`:br Array of mnemonic words --- ### **`EmbeddedWallet.mnemonicToPrivateKeyHex(words: string[], password?: string) → string`** Convert mnemonic to private key hex. **Parameters:** - `words` (string [] ): Array of mnemonic words - `password?` (string): Optional password **Example:** ```typescript const privateKeyHex = EmbeddedWallet.mnemonicToPrivateKeyHex( ['abandon', 'abandon', ...], '' ) console.log('Private key:', privateKeyHex) ``` **Returns:** `string`:br Private key in hex format --- ### **`EmbeddedWallet.privateKeyHexToBech32(privateKeyHex: string) → string`** Convert private key hex to bech32. **Parameters:** - `privateKeyHex` (string): Private key in hex format **Example:** ```typescript const bech32Key = EmbeddedWallet.privateKeyHexToBech32(privateKeyHex) console.log('Bech32 key:', bech32Key) ``` **Returns:** `string`:br Private key in bech32 format (xprv) --- ### **`EmbeddedWallet.privateKeyBech32ToPrivateKeyHex(bech32Key: string) → string`** Convert private key bech32 to hex. **Parameters:** - `bech32Key` (string): Private key in bech32 format **Example:** ```typescript const hexKey = EmbeddedWallet.privateKeyBech32ToPrivateKeyHex('xprv1...') console.log('Hex key:', hexKey) ``` **Returns:** `string`:br Private key in hex format --- ## CardanoCliWallet Wallet class for working with keys created by Cardano CLI. Useful for server-side applications. ### **`CardanoCliWallet.getAddressBech32() → string`** Get wallet address in bech32 format. **Example:** ```typescript import { CardanoCliWallet, NETWORK_ID, ProviderUtils } from '@hydra-sdk/core' const blockfrostProvider = new ProviderUtils.BlockfrostProvider({ apiKey: 'your-blockfrost-api-key', network: 'preprod' }) const cliWallet = new CardanoCliWallet({ networkId: NETWORK_ID.PREPROD, skey: '5820245120cdf333f8ea6910922b3f05bcbc0d5c8e8486ca94c020623d5cca822e04', vkey: '5820216f72947d1b97d56825c5f9f8a2e6f14234c02171853264f2f552a2685b25e0', fetcher: blockfrostProvider.fetcher, submitter: blockfrostProvider.submitter }) const address = cliWallet.getAddressBech32() console.log('Wallet address:', address) ``` **Returns:** `string`:br Address in bech32 format --- ### **`CardanoCliWallet.getNetworkId() → number`** Get current network ID. **Example:** ```typescript const networkId = cliWallet.getNetworkId() console.log('Network ID:', networkId) // 0 or 1 ``` **Returns:** `number` - `0`: Testnet - `1`: Mainnet --- ### **`CardanoCliWallet.signTx(txCbor: string, partialSign?: boolean) → Promise`** Sign a transaction. **Parameters:** - `txCbor` (string): Transaction in CBOR hex format - `partialSign?` (boolean): Perform partial signing. Default: false **Example:** ```typescript const signedTx = await cliWallet.signTx(unsignedTxCbor, false) console.log('Signed transaction:', signedTx) ``` **Returns:** `Promise`:br Signed transaction in CBOR hex format --- ### **`CardanoCliWallet.submitTx(txCbor: string) → Promise`** Submit signed transaction to blockchain. **Parameters:** - `txCbor` (string): Signed transaction in CBOR hex format **Example:** ```typescript const txHash = await cliWallet.submitTx(signedTxCbor) console.log('Transaction hash:', txHash) ``` **Returns:** `Promise`:br Transaction hash --- ### **`CardanoCliWallet.queryUTxOs(address: string) → Promise`** Query UTxOs for an address. **Parameters:** - `address` (string): Cardano address **Example:** ```typescript const address = cliWallet.getAddressBech32() const utxos = await cliWallet.queryUTxOs(address) console.log('UTxOs:', utxos) ``` **Returns:** `Promise`:br Array of UTxOs --- ## Provider Classes ### **`BlockfrostProvider`** Blockfrost provider for querying blockchain data. **Constructor:** ```typescript import { ProviderUtils } from '@hydra-sdk/core' const blockfrostProvider = new ProviderUtils.BlockfrostProvider({ apiKey: 'your-blockfrost-api-key', network: 'preprod' // or 'mainnet' }) // Access fetcher and submitter const fetcher = blockfrostProvider.fetcher const submitter = blockfrostProvider.submitter // Use with wallet const wallet = new AppWallet({ networkId: NETWORK_ID.PREPROD, key: { type: 'mnemonic', words: mnemonic }, fetcher: blockfrostProvider.fetcher, submitter: blockfrostProvider.submitter }) ``` --- ### **`OgmiosProvider`** Ogmios provider for querying blockchain data. **Constructor:** ```typescript import { ProviderUtils } from '@hydra-sdk/core' const ogmiosProvider = new ProviderUtils.OgmiosProvider({ apiEndpoint: 'https://preprod.cardano-rpc.hydrawallet.app', network: 'preprod' // or 'mainnet' }) // Access fetcher and submitter const fetcher = ogmiosProvider.fetcher const submitter = ogmiosProvider.submitter // Use with wallet const wallet = new AppWallet({ networkId: NETWORK_ID.PREPROD, key: { type: 'mnemonic', words: mnemonic }, fetcher: ogmiosProvider.fetcher, submitter: ogmiosProvider.submitter }) ``` --- ### **`DemeterProvider`** :badge{text="New in v1.3.2" type="tip"} Provider for [Demeter](https://demeter.run){rel=""nofollow""}'s Blockfrost-compatible hosted endpoints. Extends `BlockfrostProvider`, so it exposes the same `fetcher`/`submitter` surface. Authentication is embedded in the endpoint subdomain via `authToken`. **Constructor:** ```typescript import { ProviderUtils } from '@hydra-sdk/core' const demeterProvider = new ProviderUtils.DemeterProvider({ authToken: 'blockfrost102lx3ckhzvkjjh7677g', network: 'preprod' // 'mainnet' | 'preprod' | 'preview' }) // Same fetcher / submitter interface as BlockfrostProvider const utxos = await demeterProvider.fetcher.fetchAddressUTxOs(address) // Use with wallet const wallet = new AppWallet({ networkId: NETWORK_ID.PREPROD, key: { type: 'mnemonic', words: mnemonic }, fetcher: demeterProvider.fetcher, submitter: demeterProvider.submitter }) ``` The endpoint is built as `https://{authToken}.cardano-{network}.blockfrost-m1.demeter.run/api/v{apiVersion ?? 0}`. --- ## Utility Functions ### **`AddressUtils.getPubkeyHashFromAddress(address: string) → string`** Extract public key hash from a Cardano address. **Parameters:** - `address` (string): Cardano address (bech32) **Example:** ```typescript import { AddressUtils } from '@hydra-sdk/core' const pubkeyHash = AddressUtils.getPubkeyHashFromAddress('addr_test1qz2fxv2umyhttkxyxp8x0...') console.log('Public key hash:', pubkeyHash) // => "00000000000000000000000000000000000000000000000000000000" ``` **Returns:** `string`:br Public key hash in hex format --- ### **`AddressUtils.isValidAddress(address: string | Uint8Array, type?: 'bech32' | 'hex' | 'bytes') → boolean`** Check if a Cardano address is valid. `type` defaults to `'bech32'`. **Parameters:** - `address` (string | Uint8Array): Address to verify - `type?` ('bech32' | 'hex' | 'bytes'): Input encoding. Default: 'bech32' **Example:** ```typescript import { AddressUtils } from '@hydra-sdk/core' const isValid = AddressUtils.isValidAddress('addr_test1qz2fxv2umyhttkxyxp8x0...') console.log('Valid:', isValid) // => true or false ``` **Returns:** `boolean`:br True if address is valid, false otherwise --- ### **`ValidationUtils.isValidTxOutput(output: TxOutput) → boolean`** :badge{text="New in v1.4.0" type="tip"} Check if a transaction output is valid. **Parameters:** - `output` (TxOutput): Transaction output to verify **Example:** ```typescript import { ValidationUtils } from '@hydra-sdk/core' const isValid = ValidationUtils.isValidTxOutput(txOutput) console.log('Output valid:', isValid) ``` **Returns:** `boolean`:br True if output is valid --- ### **`TimeUtils.slotToBeginUnixTime(slot: number, config?: SlotConfig) → number`** Convert slot number to Unix timestamp (start of slot). **Parameters:** - `slot` (number): Slot number - `config?` (SlotConfig): Optional slot configuration **Example:** ```typescript import { TimeUtils } from '@hydra-sdk/core' const unixTime = TimeUtils.slotToBeginUnixTime(45700800) console.log('Unix time:', unixTime) // => 1671292800000 (milliseconds) ``` **Returns:** `number`:br Unix timestamp (milliseconds) --- ### **`TimeUtils.unixTimeToEnclosingSlot(unixTime: number, config?: SlotConfig) → number`** Convert Unix timestamp to enclosing slot number. **Parameters:** - `unixTime` (number): Unix timestamp (milliseconds) - `config?` (SlotConfig): Optional slot configuration **Example:** ```typescript import { TimeUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core' const slot = TimeUtils.unixTimeToEnclosingSlot(Date.now(), SLOT_CONFIG_NETWORK.PREPROD) console.log('Current slot:', slot) ``` **Returns:** `number`:br Slot number enclosing the Unix time --- ### **`TimeUtils.resolveSlotNo(network: Network, milliseconds?: number) → string`** Resolve slot number for a network at a given time. **Parameters:** - `network` (Network): Target network (`'MAINNET'` | `'PREPROD'` | `'PREVIEW'`) - `milliseconds?` (number): Timestamp in milliseconds. Default: `Date.now()` **Example:** ```typescript import { TimeUtils } from '@hydra-sdk/core' const slotNo = TimeUtils.resolveSlotNo('PREPROD') console.log('Slot number:', slotNo) ``` **Returns:** `string`:br Slot number as string --- ### **`TimeUtils.resolveEpochNo(network: Network, milliseconds?: number) → number`** Resolve epoch number for a network at a given time. **Parameters:** - `network` (Network): Target network (`'MAINNET'` | `'PREPROD'` | `'PREVIEW'`) - `milliseconds?` (number): Timestamp in milliseconds. Default: `Date.now()` **Example:** ```typescript import { TimeUtils } from '@hydra-sdk/core' const epochNo = TimeUtils.resolveEpochNo('PREPROD') console.log('Epoch number:', epochNo) ``` **Returns:** `number`:br Epoch number --- ### **`TimeUtils.buildHydraSlotConfig(startTimestamp: number, options?: object) → SlotConfig`** Build Hydra slot configuration anchored at a Head start timestamp. **Parameters:** - `startTimestamp` (number): Head start timestamp in milliseconds (required) - `options?` (object): Optional overrides for the slot configuration **Example:** ```typescript import { TimeUtils } from '@hydra-sdk/core' const config = TimeUtils.buildHydraSlotConfig(Date.now()) console.log('Slot config:', config) ``` **Returns:** `SlotConfig`:br Slot configuration for Hydra --- ### **`PolicyUtils.buildPolicyScriptFromPubkey(pubkey: string) → string`** Build minting policy script from a public key script. **Parameters:** - `pubkey` (string): Public key hex **Example:** ```typescript import { PolicyUtils } from '@hydra-sdk/core' const policyScript = PolicyUtils.buildPolicyScriptFromPubkey('a1b2c3...') console.log('Policy script:', policyScript) ``` **Returns:** `string`:br Policy script in hex format --- ### **`PolicyUtils.buildMintingPolicyScriptFromAddress(address: string) → string`** Build minting policy script from an address. **Parameters:** - `address` (string): Cardano address (bech32) **Example:** ```typescript import { PolicyUtils } from '@hydra-sdk/core' const policyScript = PolicyUtils.buildMintingPolicyScriptFromAddress('addr_test1...') console.log('Policy script:', policyScript) ``` **Returns:** `string`:br Policy script in hex format --- ### **`PolicyUtils.buildMintingPolicyScriptFromKeyHash(keyHash: string) → string`** Build minting policy script from a key hash. **Parameters:** - `keyHash` (string): Key hash in hex **Example:** ```typescript import { PolicyUtils } from '@hydra-sdk/core' const policyScript = PolicyUtils.buildMintingPolicyScriptFromKeyHash('a1b2c3...') console.log('Policy script:', policyScript) ``` **Returns:** `string`:br Policy script in hex format --- ### **`PolicyUtils.policyIdFromNativeScript(nativeScript: string) → string`** Extract policy ID from a native script. **Parameters:** - `nativeScript` (string): Native script in hex format **Example:** ```typescript import { PolicyUtils } from '@hydra-sdk/core' const policyId = PolicyUtils.policyIdFromNativeScript(nativeScriptHex) console.log('Policy ID:', policyId) ``` **Returns:** `string`:br Policy ID in hex format --- ### **`bytesToHex(bytes: Uint8Array) → string`** Convert bytes to hex string. **Parameters:** - `bytes` (Uint8Array): Byte data **Example:** ```typescript import { bytesToHex } from '@hydra-sdk/core' const hexString = bytesToHex(new Uint8Array([1, 2, 3])) console.log('Hex:', hexString) // => "010203" ``` **Returns:** `string`:br Hex string --- ### **`hexToBytes(hex: string) → Uint8Array`** Convert hex string to bytes. **Parameters:** - `hex` (string): Hex string **Example:** ```typescript import { hexToBytes } from '@hydra-sdk/core' const bytes = hexToBytes('010203') console.log('Bytes:', bytes) // => Uint8Array [ 1, 2, 3 ] ``` **Returns:** `Uint8Array`:br Byte data --- ### **`stringToHex(str: string) → string`** Convert UTF-8 string to hex string. **Parameters:** - `str` (string): UTF-8 string **Example:** ```typescript import { stringToHex } from '@hydra-sdk/core' const hex = stringToHex('Hello') console.log('Hex:', hex) // => "48656c6c6f" ``` **Returns:** `string`:br Hex string --- ### **`hexToString(hex: string) → string`** Convert hex string to UTF-8 string. **Parameters:** - `hex` (string): Hex string **Example:** ```typescript import { hexToString } from '@hydra-sdk/core' const str = hexToString('48656c6c6f') console.log('String:', str) // => "Hello" ``` **Returns:** `string`:br UTF-8 string --- ### **`toBytes(data: string | Uint8Array) → Uint8Array`** Convert hex string or UTF-8 string to bytes. **Parameters:** - `data` (string | Uint8Array): Hex string or UTF-8 string **Example:** ```typescript import { toBytes } from '@hydra-sdk/core' const bytes1 = toBytes('48656c6c6f') const bytes2 = toBytes('Hello') ``` **Returns:** `Uint8Array`:br Byte data --- ### **`fromUTF8(str: string) → string`** Convert UTF-8 string to hex string. **Parameters:** - `str` (string): UTF-8 string **Example:** ```typescript import { fromUTF8 } from '@hydra-sdk/core' const hex = fromUTF8('Hello') console.log('Hex:', hex) ``` **Returns:** `string`:br Hex string --- ### **`toUTF8(hex: string) → string`** Convert hex string to UTF-8 string. **Parameters:** - `hex` (string): Hex string **Example:** ```typescript import { toUTF8 } from '@hydra-sdk/core' const str = toUTF8('48656c6c6f') console.log('String:', str) ``` **Returns:** `string`:br UTF-8 string --- ### **`MetadataUtils.metadataObjToMetadatum(obj: any) → TransactionMetadatum`** Convert metadata object to CardanoWASM.TransactionMetadatum. **Note:** - Maximum metadata `bytes` size: 64 bytes - Maximum metadata `text` size: 64 characters **Parameters:** - `obj` (any): Metadata object **Example:** ```typescript import { MetadataUtils } from '@hydra-sdk/core' const metadata = MetadataUtils.metadataObjToMetadatum({ '1': 'Hello', '2': 12345 }) console.log('Metadata:', metadata) ``` **Returns:** `TransactionMetadatum`:br Metadata in CardanoWASM format --- ### **`KeysUtils.cardanoCliKeygen() → { sk: CardanoCLiSkey; vk: CardanoCLiVkey }`** Generate ed25519 key pair compatible with Cardano CLI. **Example:** ```typescript import { KeysUtils } from '@hydra-sdk/core' const { sk, vk } = KeysUtils.cardanoCliKeygen() console.log('Secret Key (cborHex):', sk.cborHex) console.log('Verification Key (cborHex):', vk.cborHex) ``` **Returns:** `{ sk: CardanoCLiSkey; vk: CardanoCLiVkey }` - `sk.cborHex`: Signing key in CBOR hex format - `vk.cborHex`: Verification key in CBOR hex format --- ### **`KeysUtils.hydraCliKeygen() → { sk: HydraCliSkey; vk: HydraCliVkey }`** Generate ed25519 key pair compatible with Hydra CLI. **Example:** ```typescript import { KeysUtils } from '@hydra-sdk/core' const { sk, vk } = KeysUtils.hydraCliKeygen() console.log('Secret Key:', sk) console.log('Verification Key:', vk) ``` **Returns:** `{ sk: object; vk: object }`:br Hydra key pair --- ### **`KeysUtils.genVkey(sk: CardanoCLiSkey) → CardanoCLiVkey`** Generate verification key from signing key. **Parameters:** - `sk` (CardanoCLiSkey): Signing key **Example:** ```typescript import { KeysUtils } from '@hydra-sdk/core' const { sk } = KeysUtils.cardanoCliKeygen() const vk = KeysUtils.genVkey(sk) console.log('Verification Key:', vk.cborHex) ``` **Returns:** `CardanoCLiVkey`:br Verification key --- ### **`DatumUtils.mkInt(value: number | bigint) → PlutusData`** Create PlutusData from an integer. **Parameters:** - `value` (number | bigint): Integer value **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const plutusInt = DatumUtils.mkInt(42) console.log('PlutusData:', plutusInt) ``` **Returns:** `PlutusData`:br PlutusData representing an integer --- ### **`DatumUtils.mkBytes(hex: string) → PlutusData`** Create PlutusData from hex string representing bytes. **Parameters:** - `hex` (string): Hex string **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const plutusBytes = DatumUtils.mkBytes('a1b2c3') console.log('PlutusData:', plutusBytes) ``` **Returns:** `PlutusData`:br PlutusData representing bytes --- ### **`DatumUtils.mkConstr(index: number, fields: PlutusData[]) → PlutusData`** Create PlutusData constructor. **Parameters:** - `index` (number): Constructor index - `fields` (PlutusData [] ): Array of PlutusData fields **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const constr = DatumUtils.mkConstr(0, [DatumUtils.mkInt(1), DatumUtils.mkInt(2)]) console.log('Constructor:', constr) ``` **Returns:** `PlutusData`:br PlutusData constructor --- ### **`DatumUtils.mkMap(pairs: [PlutusData, PlutusData][]) → PlutusData`** Create PlutusData map. **Parameters:** - `pairs` (\[PlutusData, PlutusData] [] ): Array of key-value pairs **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const map = DatumUtils.mkMap([ [DatumUtils.mkInt(1), DatumUtils.mkBytes('a1b2')], [DatumUtils.mkInt(2), DatumUtils.mkBytes('c3d4')] ]) console.log('Map:', map) ``` **Returns:** `PlutusData`:br PlutusData map --- ### **`CostModels.buildCostModels(models: { plutusV1?: number[]; plutusV2?: number[]; plutusV3?: number[] }) → Costmdls`** Build cost models for Cardano. At least one of `plutusV1` / `plutusV2` / `plutusV3` must be provided, otherwise it throws `At least one cost model must be provided`. For zero-config defaults use `CostModels.defaultCostModels`. **Parameters:** - `models` (object): Cost model lists keyed by Plutus language version **Example:** ```typescript import { CostModels } from '@hydra-sdk/core' // Ready-made defaults (all three Plutus versions) const defaults = CostModels.defaultCostModels // Or build from explicit cost model lists const costModels = CostModels.buildCostModels({ plutusV3: [/* ...cost model integers... */] }) console.log('Cost Models:', costModels) ``` **Returns:** `Costmdls`:br Cost models --- ## UTxO Conversion Utilities ### **`convertUTxOObjectToUTxO(utxoObject: UTxOObject) → UTxO[]`** Convert a UTxO object (snapshot format) to an array of `UTxO` items. **Example:** ```typescript import { Converter } from '@hydra-sdk/core' const utxos = Converter.convertUTxOObjectToUTxO(snapshotUtxo) console.log('UTxO count:', utxos.length) ``` **Returns:** `UTxO[]` --- ### **`convertUTxOObjectToUTxOWithOptions(utxoObject: UTxOObject, options?: ConvertOptions) → UTxO[]`** :badge{text="New in v1.3.0" type="tip"} Same as `convertUTxOObjectToUTxO` but accepts an options object for fine-tuning the internal datum deserialization cache. **Parameters:** - `utxoObject` (UTxOObject): Snapshot UTxO object - `options?`: - `maxDatumCacheSize?` (number): Max number of deserialized `PlutusData` entries to cache. `0` disables caching. Default: `1024` **Why it matters:** When processing large snapshots (thousands of UTxOs) containing repeated inline datums, the cache avoids redundant WASM deserialization calls. The LRU eviction policy keeps memory usage bounded. **Example:** ```typescript import { Converter } from '@hydra-sdk/core' // Default — cache up to 1024 datum entries const utxos = Converter.convertUTxOObjectToUTxOWithOptions(snapshotUtxo) // Larger cache for snapshots with many unique inline datums const utxos = Converter.convertUTxOObjectToUTxOWithOptions(snapshotUtxo, { maxDatumCacheSize: 4096 }) // Disable cache entirely const utxos = Converter.convertUTxOObjectToUTxOWithOptions(snapshotUtxo, { maxDatumCacheSize: 0 }) ``` **Returns:** `UTxO[]` --- ### **`convertUTxOToUTxOObject(utxos: UTxO[]) → UTxOObject`** Convert a `UTxO[]` array back to the snapshot object format. **Example:** ```typescript import { Converter } from '@hydra-sdk/core' const utxoObj = Converter.convertUTxOToUTxOObject(utxos) ``` **Returns:** `UTxOObject` --- ## Constants ### **`NETWORK_ID`** Network ID constants for different Cardano networks. `NETWORK_ID` is a `Record` keyed by `MAINNET`, `PREPROD`, and `PREVIEW` (there is no `TESTNET` key). **Example:** ```typescript import { NETWORK_ID } from '@hydra-sdk/core' // Available network IDs NETWORK_ID.MAINNET // 1 NETWORK_ID.PREPROD // 0 NETWORK_ID.PREVIEW // 0 ``` --- ## Related Documentation - [Transaction Builder API](https://hydrasdk.com/api/transaction) - Build transactions - [Hydra Bridge API](https://hydrasdk.com/api/bridge) - Hydra Layer 2 integration # @hydra-sdk/bridge ## Classes --- ## HydraBridge Main class for connecting to and managing Hydra Heads. ### **`HydraBridge.constructor(options: InitHydraBridgeOptions) → HydraBridge`** Initialize a new Hydra Bridge instance. **Parameters:** - `options`(InitHydraBridgeOptions): - `url?`: WebSocket URL (e.g., `'ws://localhost:4001'`, `'wss://gateway/path?X-Api-Key=...'`) - `connector?`: Custom connector instance - `verbose?`: Enable verbose logging (default: false) - `history?`: Enable receiving full node history (default: false) - `noSnapshotUtxo?`: Disable snapshot UTxO visibility (default: false) - `address?`: Filter transaction outputs by specific address - `autoReconnect?`: Automatically reconnect when WebSocket drops (default: false) - `reconnectInterval?`: Milliseconds between reconnect attempts (default: 3000) - `maxReconnectAttempts?`: Max reconnect attempts; 0 = unlimited (default: 0) **Example:** ```typescript import { HydraBridge } from '@hydra-sdk/bridge' // Direct WebSocket connection const bridge = new HydraBridge({ url: 'ws://localhost:4001', verbose: true }) // With advanced options const bridge = new HydraBridge({ url: 'ws://localhost:4001', verbose: true, history: true, noSnapshotUtxo: false, address: 'addr_test1...' }) // Hydra Hub connection with API key const bridge = new HydraBridge({ url: 'wss://hydra-hub.example.com?X-Api-Key=your_api_key_here', verbose: true }) // Using custom connector const bridge = new HydraBridge({ connector: customConnector, verbose: true }) // With auto-reconnect const bridge = new HydraBridge({ url: 'ws://localhost:4001', autoReconnect: true, reconnectInterval: 3000, // retry every 3s maxReconnectAttempts: 10 // give up after 10 attempts (0 = unlimited) }) ``` **Returns:** `HydraBridge`:br New HydraBridge instance --- ## Properties ### **`HydraBridge.slotZeroTimestamp: number | null`** Unix timestamp (ms) of slot 0, derived from the `currentSlot` field of the `Greetings` message (hydra-node v1.3.0+). Use this for slot ↔ time conversion inside the Hydra Head. `null` until the first `Greetings` is received. ```typescript bridge.events.on('onMessage', payload => { if (payload.tag === 'Greetings' && bridge.slotZeroTimestamp !== null) { console.log('Slot-zero epoch:', bridge.slotZeroTimestamp) } }) ``` --- ### **`HydraBridge.lastSnapshotNumber: number`** The highest snapshot number applied to the in-memory cache. `-1` until the first snapshot arrives. Resets to `-1` on every reconnect so the cache is properly re-seeded. ```typescript console.log('Current snapshot:', bridge.lastSnapshotNumber) ``` --- ## Connection Management ### **`HydraBridge.connect() → Promise`** Connect to the Hydra Node. **Example:** ```typescript await bridge.connect() console.log('Connected to Hydra Node') ``` **Returns:** `Promise` --- ### **`HydraBridge.disconnect() → Promise`** Disconnect from the Hydra Node. **Example:** ```typescript await bridge.disconnect() console.log('Disconnected from Hydra Node') ``` **Returns:** `Promise` --- ### **`HydraBridge.connected() → boolean`** Check the connection status. **Example:** ```typescript const isConnected = bridge.connected() console.log('Connection status:', isConnected) ``` **Returns:** `boolean`:br True if connected, false otherwise --- ## Head Information ### **`HydraBridge.headInfo() → Promise`** Get information about the current Head. **Example:** ```typescript const info = await bridge.headInfo() console.log({ headId: info.headId, headStatus: info.headStatus, vkey: info.vkey }) ``` **Returns:** `Promise`:br Head information object --- ## Protocol Parameters ### **`HydraBridge.getProtocolParameters() → Promise`** Get Cardano protocol parameters. **Example:** ```typescript const protocolParams = await bridge.getProtocolParameters() console.log({ minFeeA: protocolParams.min_fee_a, minFeeB: protocolParams.min_fee_b, maxTxSize: protocolParams.max_tx_size, keyDeposit: protocolParams.key_deposit, poolDeposit: protocolParams.pool_deposit }) ``` **Returns:** `Promise`:br Protocol parameters object --- ## UTxO Management ### **`HydraBridge.querySnapshotUtxo() → Promise`** Query UTxOs in the current snapshot. **Example:** ```typescript const utxoObject = await bridge.querySnapshotUtxo() console.log('Snapshot UTxO:', utxoObject) ``` **Returns:** `Promise`:br UTxO object from snapshot --- ### **`HydraBridge.snapshotUtxoArray() → UTxO[]`** Get UTxOs array from snapshot. **Example:** ```typescript const utxoArray = bridge.snapshotUtxoArray() console.log('UTxO array:', utxoArray) ``` **Returns:** `UTxO[]`:br Array of UTxOs --- ### **`HydraBridge.getAddressBalance(address: string) → Map | null`** O(1) balance lookup from the pre-computed in-memory cache. Rebuilt automatically on every snapshot event — no I/O per call. - Returns `null` on cold start (cache not yet seeded) — caller should fall back to DB or call `querySnapshotUtxo()` first. - Returns an empty `Map` when the address is in the Head but holds no UTxOs. - Keys are asset units (`'lovelace'`, `'policyId+assetName'`); values are `bigint`. **Parameters:** - `address` (string): Cardano address (bech32) **Example:** ```typescript const balance = bridge.getAddressBalance(account.baseAddressBech32) if (balance === null) { console.log('Cache not seeded yet — wait for Greetings or call querySnapshotUtxo()') } else { const lovelace = balance.get('lovelace') ?? 0n console.log('ADA balance:', Number(lovelace) / 1_000_000) for (const [unit, qty] of balance) { if (unit !== 'lovelace') console.log(unit, qty.toString()) } } ``` **Returns:** `Map | null` --- ### **`HydraBridge.queryAddressUTxO(address: string) → Promise`** Query UTxOs for a specific address. **Parameters:** - `address` (string): Cardano address (bech32) **Example:** ```typescript const account = wallet.getAccount(0, 0) const addressUtxos = await bridge.queryAddressUTxO(account.baseAddressBech32) console.log('Address UTxOs:', addressUtxos) ``` **Returns:** `Promise`:br Array of UTxOs for the address --- ### **`HydraBridge.addressesInHead() → Promise`** Get all addresses in the current Head. **Example:** ```typescript const addresses = await bridge.addressesInHead() console.log('Addresses in Head:', addresses) ``` **Returns:** `Promise`:br Array of addresses --- ## Transaction Operations ### **`HydraBridge.submitTxSync(transaction: Transaction, options?: SubmitOptions) → Promise`** Submit transaction and wait for confirmation. **Parameters:** - `transaction` (Transaction): Transaction to submit - `options?`(SubmitOptions): Submission options - `timeout?`: Timeout in milliseconds **Example:** ```typescript const result = await bridge.submitTxSync( { type: 'Witnessed Tx ConwayEra', description: 'Ledger Cddl Format', cborHex: signedTxCbor, txId: transactionId }, { timeout: 30000 } ) console.log({ txId: result.txId, isValid: result.isValid, isConfirmed: result.isConfirmed }) ``` **Returns:** `Promise`:br Transaction result object --- ### **`HydraBridge.submitTx(transaction: Transaction, callback: SubmitTxCallback, options?: SubmitOptions) → void`** Submit a transaction using a Node.js error-first callback. Non-blocking — returns immediately. **Parameters:** - `transaction` (Transaction): Transaction to submit - `callback` `(error: SubmitTxError | null, result: SubmitTxResult | null) => void`: Called once when the transaction is confirmed, rejected, or times out - `options?` (SubmitOptions): `{ timeout?: number }` — default 30 000ms **Example:** ```typescript bridge.submitTx( { type: 'Witnessed Tx ConwayEra', description: '', cborHex: signedTxCbor, txId }, (error, result) => { if (error) { console.error('Submit failed:', error.reason) return } console.log('Confirmed in snapshot:', result!.result?.snapshot?.number) }, { timeout: 30000 } ) ``` **Returns:** `void` --- ### **`HydraBridge.commit(data: CommitData) → Promise`** Commit UTxOs into the Head. **Parameters:** - `data` (CommitData): Commit data object **Example:** ```typescript const result = await bridge.commit({ cborHex: commitTxCbor, description: 'Commit transaction' }) console.log('Commit result:', result) ``` **Returns:** `Promise` --- ### **`HydraBridge.submitCardanoTransaction(data: TransactionData) → Promise`** Submit transaction to Cardano network. **Parameters:** - `data` (TransactionData): Transaction data object **Example:** ```typescript const result = await bridge.submitCardanoTransaction({ cborHex: txCbor, description: 'Cardano transaction' }) console.log('Cardano transaction submitted:', result) ``` **Returns:** `Promise` --- ## Commands ### **`HydraBridge.commands.init() → void`** Initialize a new Hydra Head. **Example:** ```typescript bridge.commands.init() ``` --- ### **`HydraBridge.commands.close() → void`** Close the Hydra Head. **Example:** ```typescript bridge.commands.close() ``` --- ### **`HydraBridge.commands.safeClose() → void`** Close the Head only when it holds no non-ADA assets. The node replies with `InvalidInput` when assets are present. **Example:** ```typescript bridge.commands.safeClose() ``` --- ### **`HydraBridge.commands.sideLoadSnapshot(snapshot) → void`** Side-load a confirmed snapshot into the Head. **Example:** ```typescript bridge.commands.sideLoadSnapshot(confirmedSnapshot) ``` --- ### **`HydraBridge.commands.partialFanout(utxo) → void`** Fan out a client-selected subset of a closed Head's UTxO. ::warning Requires a hydra-node newer than 2.3.0. Older nodes reply with `InvalidInput`. :: **Example:** ```typescript bridge.commands.partialFanout(utxoToFanout) ``` --- ### **`HydraBridge.submitL2Tx(tx, options?) → Promise`** Submit an L2 transaction over `POST /transaction` and let the node return the verdict, instead of racing WebSocket messages against a client-side timeout. Prefer this over `submitTxSync()`. **Example:** ```typescript const res = await bridge.submitL2Tx(tx) switch (res.tag) { case 'SubmitTxConfirmed': console.log('snapshot', res.snapshotNumber); break case 'SubmitTxInvalid': console.error(res.validationError); break case 'SubmitTxRejected': console.error(res.reason); break // node out of sync case 'SubmitTxSubmitted': break // accepted, not yet confirmed } ``` --- ### **`HydraBridge.pendingDeposits() → Promise`** Transaction ids of deposits observed on chain but not yet included in a snapshot. **Example:** ```typescript const pending = await bridge.pendingDeposits() ``` --- ### **`HydraBridge.recoverDeposit(depositTxId, options?) → Promise`** Recover a pending deposit back to L1. Deposits stay recoverable after a Head closes, so this is also how you reclaim funds from a previous Head. **Example:** ```typescript await bridge.recoverDeposit(depositTxId) ``` --- ### **`HydraBridge.getRawProtocolParameters() → Promise`** The node's protocol parameters unmodified — including `costModels` and `protocolVersion`, which `getProtocolParameters()` drops. Use this when budgeting Plutus ExUnits. **Example:** ```typescript const raw = await bridge.getRawProtocolParameters() raw.costModels.PlutusV3 // 350 entries under PV11 ``` --- ### **`HydraBridge.commands.fanout() → void`** Perform fanout after closing Head. **Example:** ```typescript bridge.commands.fanout() ``` --- ### **`HydraBridge.commands.contest() → void`** Contest the Head closure. **Example:** ```typescript bridge.commands.contest() ``` --- ### **`HydraBridge.commands.recover(recoverTxId: string) → void`** Recover from a specific transaction. **Parameters:** - `recoverTxId` (string): Transaction hash to recover from **Example:** ```typescript bridge.commands.recover('tx_hash_here') ``` --- ### **`HydraBridge.commands.newTx(cborHex: string, description: string, callback?: Function) → void`** Send a new transaction to the Head. **Parameters:** - `cborHex` (string): Transaction in CBOR hex format - `description` (string): Transaction description - `callback?` (Function): Callback function when sent **Example:** ```typescript bridge.commands.newTx(signedTxCbor, 'Payment transaction', () => console.log('Transaction sent to Head') ) ``` --- ### **`HydraBridge.commands.decommit(payload: DecommitPayload) → Promise`** Decommit UTxOs from the Head. **Parameters:** - `payload`(DecommitPayload): Decommit data - `cborHex`: Transaction CBOR hex - `txId`: Transaction ID - `timeout?`: Timeout value **Example:** ```typescript const result = await bridge.commands.decommit({ cborHex: decommitTxCbor, txId: transactionId, timeout: 30000 }) console.log('Decommit result:', result) ``` **Returns:** `Promise` --- ### **`HydraBridge.commands.initSync(retry?: number, interval?: number) → Promise`** Initialize Head with retry logic. **Parameters:** - `retry?` (number): Number of retries. Default: 3 - `interval?` (number): Interval between retries (ms). Default: 20000 **Example:** ```typescript const result = await bridge.commands.initSync(3, 20000) console.log('Head initialized:', result) ``` **Returns:** `Promise` --- ## Connectors ### **`WebsocketConnector`** Basic WebSocket connector for direct connection to Hydra Node. **Constructor Parameters:** - `websocketUrl` (string): WebSocket URL of the Hydra Node - `history?` (boolean): Enable receiving full node history (default: false) - `noSnapshotUtxo?` (boolean): Disable snapshot UTxO visibility (default: false) - `address?` (string): Filter transaction outputs by specific address **Constructor:** ```typescript import { WebsocketConnector } from '@hydra-sdk/bridge' // Basic connection const connector = new WebsocketConnector({ websocketUrl: 'ws://localhost:4001' }) // With advanced options const connector = new WebsocketConnector({ websocketUrl: 'ws://localhost:4001', history: true, noSnapshotUtxo: false, address: 'addr_test1...' }) // Hydra Hub connection with API key in URL const connector = new WebsocketConnector({ websocketUrl: 'wss://hydra-hub.example.com?X-Api-Key=your_api_key_here' }) // Combine API key with other options const connector = new WebsocketConnector({ websocketUrl: 'wss://hydra-hub.example.com?X-Api-Key=your_api_key_here', history: true, address: 'addr_test1...' }) const bridge = new HydraBridge({ connector: connector, verbose: true }) ``` --- ### **`HexcoreConnector`** Advanced connector for Hexcore API integration with Socket.IO authentication. **Constructor:** ```typescript import { HexcoreConnector } from '@hydra-sdk/bridge' // Constructor is positional: new HexcoreConnector(socketIoUrl, options?) const connector = new HexcoreConnector('wss://api.hexcore.io/hydra', { socketIoOptions: { auth: { token: 'your_jwt_token' }, transports: ['websocket'], timeout: 20000 }, namespace: 'hydra' }) const bridge = new HydraBridge({ connector: connector, verbose: true }) await bridge.connect() ``` --- ## Event Handling Bridge provides comprehensive event handling through the `events` property: ### **Connection Events** ```typescript // Connection established bridge.events.on('onConnected', () => { console.log('Connected to Hydra Node') }) // Connection lost bridge.events.on('onDisconnected', () => { console.log('Disconnected from Hydra Node') }) // Connection error bridge.events.on('onConnectError', error => { console.error('Connection error:', error) }) ``` --- ### **Head Lifecycle Events** ```typescript bridge.events.on('onMessage', payload => { console.log('Message received:', payload.tag, payload.timestamp) switch (payload.tag) { case 'Greetings': console.log('Greeting from Head:', { headStatus: payload.headStatus, headId: payload.hydraHeadId, // v1.3.0 fields currentSlot: payload.currentSlot, chainSyncedStatus: payload.chainSyncedStatus, nodeVersion: payload.hydraNodeVersion }) // slotZeroTimestamp is automatically derived when currentSlot is present console.log('Slot-zero timestamp:', bridge.slotZeroTimestamp) break case 'HeadIsOpen': console.log('Head is open and ready for transactions!') break case 'HeadIsClosed': console.log('Head closed, performing fanout...') break case 'HeadIsFinalized': console.log('Head lifecycle completed') break case 'TxValid': console.log('Transaction is valid:', payload.transaction) break case 'TxInvalid': console.log('Transaction is invalid:', { transaction: payload.transaction, error: payload.validationError }) break case 'SnapshotConfirmed': console.log('Snapshot confirmed') break case 'CommitFinalized': console.log('Deposit finalized:', payload.depositTxId) break case 'DecommitApproved': console.log('Decommit approved:', payload.decommitTxId) break case 'DecommitFinalized': console.log('Decommit finalized') break case 'CommandFailed': console.error('Command failed:', payload.clientInput) break } }) ``` --- ## Types and Interfaces ### InitHydraBridgeOptions ```typescript type InitHydraBridgeOptions = { verbose?: boolean /** Automatically reconnect on WebSocket drop. Default: false */ autoReconnect?: boolean /** Milliseconds between reconnect attempts. Default: 3000 */ reconnectInterval?: number /** Max reconnect attempts; 0 = unlimited. Default: 0 */ maxReconnectAttempts?: number } & ( | { url: string; history?: boolean; noSnapshotUtxo?: boolean; address?: string } | { connector: HydraConnector } ) ``` ### SubmitTxResult / SubmitTxError ```typescript type SubmitTxResult = { txId: string isValid: boolean isConfirmed: boolean result: Readonly | null } type SubmitTxError = { txId: string reason: string tag: string } ``` ### HydraPayload ```typescript interface HydraPayload { tag: HydraHeadTag timestamp: string seq?: number [key: string]: any } ``` ### HydraHeadStatus ```typescript enum HydraHeadStatus { Idle = 'Idle', Open = 'Open', Closed = 'Closed', FanoutPossible = 'FanoutPossible', /** Requires a hydra-node newer than 2.3.0 (selective partial fanout). */ FanningOut = 'FanningOut' } ``` ### Protocol ```typescript interface Protocol { min_fee_a: number min_fee_b: number max_tx_size: number key_deposit: string pool_deposit: string [key: string]: any } ``` --- ## Complete Examples ### Basic Head Management ```typescript import { HydraBridge } from '@hydra-sdk/bridge' async function manageHydraHead() { const bridge = new HydraBridge({ url: 'ws://localhost:4001', verbose: true }) // Set up event handlers before connecting bridge.events.on('onConnected', () => { console.log('Connected! Initializing Head...') bridge.commands.init() }) bridge.events.on('onMessage', payload => { switch (payload.tag) { case 'HeadIsOpen': console.log('Head is open and ready for transactions!') break case 'TxValid': console.log('Transaction confirmed:', payload.transaction.txId) break case 'HeadIsClosed': console.log('Head closed, performing fanout...') bridge.commands.fanout() break case 'HeadIsFinalized': console.log('Head lifecycle completed') bridge.disconnect() break } }) // Connect to start the process await bridge.connect() } ``` ### Processing Transactions in Hydra Head ```typescript import { HydraBridge } from '@hydra-sdk/bridge' import { TxBuilder } from '@hydra-sdk/transaction' import { AppWallet } from '@hydra-sdk/core' async function processHydraTransactions() { const bridge = new HydraBridge({ url: 'ws://localhost:4001', verbose: true }) const wallet = new AppWallet({ networkId: 0, key: { type: 'mnemonic', words: AppWallet.brew() } }) bridge.events.on('onConnected', async () => { // Get protocol parameters const protocolParams = await bridge.getProtocolParameters() // Create transaction builder const txBuilder = new TxBuilder({ isHydra: true, params: protocolParams }) // Get account and UTxOs const account = wallet.getAccount(0, 0) const addressUtxos = await bridge.queryAddressUTxO(account.baseAddressBech32) if (addressUtxos.length === 0) { console.log('No UTxOs found for address') return } // Build and sign transaction const tx = await txBuilder .setInputs(addressUtxos) .txOut(account.baseAddressBech32, [ { unit: 'lovelace', quantity: '1000000' } ]) .changeAddress(account.baseAddressBech32) .complete() const signedTx = await wallet.signTx(tx.to_hex(), false, 0, 0) // Submit to Hydra Head const result = await bridge.submitTxSync( { type: 'Witnessed Tx ConwayEra', description: 'Ledger Cddl Format', cborHex: signedTx, txId: tx.id }, { timeout: 30000 } ) console.log('Transaction successful:', result) }) await bridge.connect() } ``` ### Advanced Hexcore Integration ```typescript import { HydraBridge, HexcoreConnector } from '@hydra-sdk/bridge' async function connectToHexcore() { const connector = new HexcoreConnector('wss://api.hexcore.io/hydra', { socketIoOptions: { auth: { token: 'your_jwt_token' }, transports: ['websocket'], timeout: 20000 }, namespace: 'hydra' }) const bridge = new HydraBridge({ connector: connector, verbose: true }) bridge.events.on('onConnected', () => { console.log('Authenticated and connected to Hexcore') }) bridge.events.on('onConnectError', error => { console.error('Hexcore connection error:', error) }) await bridge.connect() } ``` ### Advanced Hydra Hub Integration ```typescript import { HydraBridge } from '@hydra-sdk/bridge' async function connectWithApiKey() { // Pass API key directly in URL const bridge = new HydraBridge({ url: 'wss://hydra-hub.example.com?X-Api-Key=your_secret_api_key', verbose: true }) bridge.events.on('onConnected', () => { console.log('Authenticated and connected to Hydra Hub') }) bridge.events.on('onConnectError', error => { console.error('Connection error:', error) }) await bridge.connect() } ``` --- ## Best Practices 1. **Event Setup**: Always set up event handlers before calling `connect()` 2. **Balance reads**: Prefer `getAddressBalance()` (O(1), no I/O) over `queryAddressUTxO()` when you only need amounts — the cache is kept fresh automatically on every `SnapshotConfirmed` 3. **Auto-reconnect**: Use `autoReconnect: true` in production; `disconnect()` cancels the reconnect loop cleanly 4. **Callback vs Promise**: Use `submitTx` (callback) when you need non-blocking fire-and-forget; use `submitTxSync` (Promise) when you need to `await` confirmation before continuing 5. **Authentication**: - For Hydra Hub: Pass API key in URL query params (`?X-Api-Key=your_key`) - For Hexcore: Use JWT tokens in Socket.IO auth options 6. **Resource Cleanup**: Always call `disconnect()` when done 7. **Slot arithmetic**: Use `bridge.slotZeroTimestamp` + `TimeUtils` from `@hydra-sdk/core` for slot ↔ time conversion inside the Head 8. **Timeout Configuration**: Set appropriate timeouts for `submitTxSync` / `submitTx` (default 30 000ms) 9. **Logging**: Enable `verbose: true` for debugging during development --- ## Related Documentation - [Core API](https://hydrasdk.com/api/core) - Wallet and utilities - [Transaction Builder API](https://hydrasdk.com/api/transaction) - Build transactions # @hydra-sdk/transaction ## 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. :badge{text="New in v1.2.0" type="tip"} - `txEvaluationMultiplier?` (number): Safety multiplier applied to evaluated exUnits before writing them back (e.g. `1.1` to over-provision by 10%). Default: `1`. :badge{text="New in v1.2.0" type="tip"} - `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:** ```typescript 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`:br 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:** ```typescript 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`:br 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:** ```typescript 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:** ```typescript // 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:** ```typescript 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. | Alias | Equivalent 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)` | ```typescript // 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:** ```typescript // 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:** ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript txBuilder.registerStake('stake_test1...') ``` ::note 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`** :badge{text="New in v1.2.0" type="tip"} Deregister (retire) a stake address, reclaiming its key deposit. **Parameters:** - `rewardAddress` (string): Stake reward address **Example:** ```typescript 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:** ```typescript txBuilder.delegateStake('stake_test1...', 'pool1...') ``` ::note 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:** ```typescript 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:** ```typescript 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:** ```typescript txBuilder.totalCollateral('5000000') ``` ::note 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:** ```typescript // 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:** ```typescript txBuilder.invalidBefore(1000000) ``` **Returns:** `TxBuilder` --- ### **`TxBuilder.invalidAfter(slot: number) → TxBuilder`** Set the transaction validity end time. **Parameters:** - `slot` (number): Slot number **Example:** ```typescript txBuilder.invalidAfter(2000000) ``` **Returns:** `TxBuilder` --- ### **`TxBuilder.complete() → Promise`** Build and complete the transaction. **Example:** ```typescript const tx = await txBuilder .setInputs(utxos) .txOut('addr_test1...', [{ unit: 'lovelace', quantity: '2000000' }]) .changeAddress(changeAddress) .complete() console.log('Transaction CBOR:', tx.to_hex()) ``` ::warning `complete()` returns a **live WASM `Transaction`** object. When you only need the serialized bytes, prefer [`completeCbor()`](https://hydrasdk.com/#txbuildercompletecbor-promisestring), 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](https://hydrasdk.com/resources/performance#txbuilder-wasm-memory-v120). :: 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 [Script evaluation](https://hydrasdk.com/#script-evaluation-exunits)). **Returns:** `Promise`:br Completed transaction (caller-owned; call `.free()` or use `completeCbor()`) --- ### **`TxBuilder.completeCbor() → Promise`** :badge{text="New in v1.2.0" type="tip"} 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:** ```typescript 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`:br Transaction CBOR hex --- ### **`TxBuilder.dispose() → void`** :badge{text="New in v1.2.0" type="tip"} 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:** ```typescript // 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 ``` ::tip 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](https://hydrasdk.com/resources/performance#txbuilder-wasm-memory-v120). :: **Returns:** `void` --- ### **`TxBuilder.reset() → TxBuilder`** Reset the builder to its initial state. **Example:** ```typescript 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:** ```typescript const minFee = txBuilder.calculateFee() console.log('Minimum fee:', minFee.to_str()) // currently always '0' ``` **Returns:** `BigNum`:br 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:** ```typescript // 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:** ```typescript 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`:br 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:** ```typescript 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, 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`): 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:** ```typescript 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:** ```typescript 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:** ```typescript import { MetadataUtils } from '@hydra-sdk/core' const metadata = MetadataUtils.metadataObjToMetadatum({ '1': 'Hello', '2': 12345 }) ``` **Returns:** `TransactionMetadatum` --- ## Types and Interfaces ### TxBuilderOptions ```typescript 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 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 :badge{text="New in v1.2.0" type="tip"} 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. ```typescript 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 } ``` ### CoinSelectionStrategy ```typescript 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 ```typescript interface Asset { unit: string // 'lovelace' or 'policyId.assetName' quantity: string // Amount as string } ``` --- ## Script evaluation (exUnits) :badge{text="New in v1.2.0" type="tip"} 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. ```typescript 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 ``` ::note **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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 } ``` --- ## Related Documentation - [Core API](https://hydrasdk.com/api/core) - Wallet and utilities - [Hydra Bridge API](https://hydrasdk.com/api/bridge) - Hydra Layer 2 integration # @hydra-sdk/cardano-wasm The Cardano WASM package provides comprehensive bindings for advanced blockchain operations, including transaction building, cryptographic utilities, metadata handling, and complete Cardano protocol support. ## CardanoWASM Main export containing all Cardano WASM functionality from the Emurgo Cardano Serialization Library. ### Import ```typescript import { CardanoWASM } from '@hydra-sdk/cardano-wasm' // Access all WASM functions and classes const { min_fee, Transaction, TransactionBuilder, Address, Value, BigNum, PlutusData, // ... and many more } = CardanoWASM ``` ### # Top-level Functions ### **`min_fee(tx, linear_fee)`** Calculate the minimum fee for a transaction: ```typescript const linearFee = CardanoWASM.LinearFee.new( CardanoWASM.BigNum.from_str('44'), CardanoWASM.BigNum.from_str('155381') ) const minFee = CardanoWASM.min_fee(transaction, linearFee) console.log('Minimum fee:', minFee.to_str()) ``` ### **`calculate_ex_units_ceil_cost(ex_units, ex_unit_prices)`** Calculate execution unit cost: ```typescript const exUnits = CardanoWASM.ExUnits.new( CardanoWASM.BigNum.from_str('14000000'), CardanoWASM.BigNum.from_str('10000000000') ) const exUnitPrices = CardanoWASM.ExUnitPrices.new( CardanoWASM.UnitInterval.new(CardanoWASM.BigNum.from_str('721'), CardanoWASM.BigNum.from_str('10000000')), CardanoWASM.UnitInterval.new(CardanoWASM.BigNum.from_str('577'), CardanoWASM.BigNum.from_str('10000')) ) const cost = CardanoWASM.calculate_ex_units_ceil_cost(exUnits, exUnitPrices) console.log('Execution cost:', cost.to_str()) ``` ### **`min_script_fee(tx, ex_unit_prices)`** Calculate minimum script fee: ```typescript const scriptFee = CardanoWASM.min_script_fee(transaction, exUnitPrices) console.log('Minimum script fee:', scriptFee.to_str()) ``` ### **`min_ref_script_fee(total_ref_scripts_size, ref_script_coins_per_byte)`** Calculate minimum reference script fee: ```typescript const refScriptCoinsPerByte = CardanoWASM.UnitInterval.new( CardanoWASM.BigNum.from_str('15'), CardanoWASM.BigNum.from_str('1') ) const refScriptFee = CardanoWASM.min_ref_script_fee(1024, refScriptCoinsPerByte) console.log('Reference script fee:', refScriptFee.to_str()) ``` ### **`min_ada_for_output(output, data_cost)`** Calculate minimum ADA required for output: ```typescript const dataCost = CardanoWASM.DataCost.new_coins_per_byte( CardanoWASM.BigNum.from_str('4310') ) const minAda = CardanoWASM.min_ada_for_output(output, dataCost) console.log('Minimum ADA:', minAda.to_str()) ``` ### # Plutus Data Functions ### **`encode_json_str_to_plutus_datum(json, schema)`** Convert JSON to Plutus datum: ```typescript const jsonData = '{"constructor": 0, "fields": [{"int": 42}]}' const datum = CardanoWASM.encode_json_str_to_plutus_datum( jsonData, CardanoWASM.PlutusDatumSchema.DetailedSchema ) console.log('Plutus datum:', datum.to_hex()) ``` ### **`decode_plutus_datum_to_json_str(datum, schema)`** Convert Plutus datum to JSON: ```typescript const json = CardanoWASM.decode_plutus_datum_to_json_str( datum, CardanoWASM.PlutusDatumSchema.DetailedSchema ) console.log('JSON data:', json) ``` ### **`hash_plutus_data(plutus_data)`** Hash Plutus data: ```typescript const dataHash = CardanoWASM.hash_plutus_data(plutusData) console.log('Data hash:', dataHash.to_hex()) ``` ### **`hash_script_data(redeemers, cost_models, datums?)`** Hash script data for transaction: ```typescript const scriptDataHash = CardanoWASM.hash_script_data( redeemers, costModels, datums ) console.log('Script data hash:', scriptDataHash.to_hex()) ``` ### # Metadata Functions ### **`encode_json_str_to_metadatum(json, schema)`** Convert JSON to transaction metadata: ```typescript const jsonMetadata = '{"674": {"msg": ["Hello", "World"]}}' const metadatum = CardanoWASM.encode_json_str_to_metadatum( jsonMetadata, CardanoWASM.MetadataJsonSchema.DetailedSchema ) ``` ### **`decode_metadatum_to_json_str(metadatum, schema)`** Convert metadata to JSON: ```typescript const json = CardanoWASM.decode_metadatum_to_json_str( metadatum, CardanoWASM.MetadataJsonSchema.DetailedSchema ) console.log('Metadata JSON:', json) ``` ### **`encode_arbitrary_bytes_as_metadatum(bytes)`** Encode bytes as metadata: ```typescript const bytes = new Uint8Array([72, 101, 108, 108, 111]) // "Hello" const metadatum = CardanoWASM.encode_arbitrary_bytes_as_metadatum(bytes) ``` ### **`decode_arbitrary_bytes_from_metadatum(metadata)`** Decode bytes from metadata: ```typescript const bytes = CardanoWASM.decode_arbitrary_bytes_from_metadatum(metadatum) console.log('Decoded bytes:', Array.from(bytes)) ``` ### # Witness Functions ### **`make_vkey_witness(tx_body_hash, sk)`** Create verification key witness: ```typescript const txBodyHash = transaction.body().hash() const privateKey = CardanoWASM.PrivateKey.from_hex('private_key_hex') const vkeyWitness = CardanoWASM.make_vkey_witness(txBodyHash, privateKey) console.log('VKey witness:', vkeyWitness.to_hex()) ``` ### **`make_daedalus_bootstrap_witness(tx_body_hash, addr, key)`** Create Daedalus bootstrap witness: ```typescript const bootstrapWitness = CardanoWASM.make_daedalus_bootstrap_witness( txBodyHash, byronAddress, legacyDaedalusKey ) ``` ### **`make_icarus_bootstrap_witness(tx_body_hash, addr, key)`** Create Icarus bootstrap witness: ```typescript const bootstrapWitness = CardanoWASM.make_icarus_bootstrap_witness( txBodyHash, byronAddress, bip32PrivateKey ) ``` ### # Hash Functions ### **`hash_auxiliary_data(auxiliary_data)`** Hash auxiliary data: ```typescript const auxDataHash = CardanoWASM.hash_auxiliary_data(auxiliaryData) console.log('Auxiliary data hash:', auxDataHash.to_hex()) ``` ### # Transaction Utilities ### **`get_implicit_input(txbody, pool_deposit, key_deposit)`** Get implicit input value: ```typescript const poolDeposit = CardanoWASM.BigNum.from_str('500000000') const keyDeposit = CardanoWASM.BigNum.from_str('2000000') const implicitInput = CardanoWASM.get_implicit_input( transactionBody, poolDeposit, keyDeposit ) console.log('Implicit input:', implicitInput.coin().to_str()) ``` ### **`get_deposit(txbody, pool_deposit, key_deposit)`** Get deposit amount: ```typescript const deposit = CardanoWASM.get_deposit( transactionBody, poolDeposit, keyDeposit ) console.log('Deposit:', deposit.to_str()) ``` ### # Encryption Functions ### **`encrypt_with_password(password, salt, nonce, data)`** Encrypt data with password: ```typescript const encrypted = CardanoWASM.encrypt_with_password( 'my_password', 'salt_string', 'nonce_string', 'data_to_encrypt' ) console.log('Encrypted:', encrypted) ``` ### **`decrypt_with_password(password, data)`** Decrypt data with password: ```typescript const decrypted = CardanoWASM.decrypt_with_password( 'my_password', encryptedData ) console.log('Decrypted:', decrypted) ``` ### # Native Script Functions ### **`encode_json_str_to_native_script(json, self_xpub, schema)`** Create native script from JSON: ```typescript const scriptJson = `{ "type": "all", "scripts": [ {"type": "sig", "keyHash": "key_hash_here"}, {"type": "before", "slot": 1000000} ] }` const nativeScript = CardanoWASM.encode_json_str_to_native_script( scriptJson, selfXpub, CardanoWASM.ScriptSchema.Wallet ) ``` ### # Utility Functions ### **`create_send_all(address, utxos, config)`** Create send-all transaction batches: ```typescript const batches = CardanoWASM.create_send_all( recipientAddress, utxos, transactionBuilderConfig ) console.log('Transaction batches:', batches.len()) ``` ### **`has_transaction_set_tag(tx_bytes)`** Check transaction set tags: ```typescript const txBytes = new Uint8Array(transactionHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))) const setTagState = CardanoWASM.has_transaction_set_tag(txBytes) console.log('Set tag state:', setTagState) ``` ## Core Classes ### Address Address handling for all Cardano address types: ```typescript // Create from Bech32 const address = CardanoWASM.Address.from_bech32('addr_test1...') // Create base address const baseAddr = CardanoWASM.BaseAddress.new( CardanoWASM.NetworkId.testnet(), CardanoWASM.Credential.from_keyhash(paymentKeyHash), CardanoWASM.Credential.from_keyhash(stakeKeyHash) ) // Convert to address const address = baseAddr.to_address() console.log('Address:', address.to_bech32()) ``` ### Value Value and multi-asset handling: ```typescript // Create ADA-only value const adaValue = CardanoWASM.Value.new( CardanoWASM.BigNum.from_str('2000000') ) // Create multi-asset value const multiAsset = CardanoWASM.MultiAsset.new() const assets = CardanoWASM.Assets.new() assets.insert( CardanoWASM.AssetName.new(Buffer.from('TokenName')), CardanoWASM.BigNum.from_str('100') ) multiAsset.insert( CardanoWASM.ScriptHash.from_hex('policy_id'), assets ) const value = CardanoWASM.Value.new_with_assets( CardanoWASM.BigNum.from_str('2000000'), multiAsset ) ``` ### BigNum Arbitrary precision numbers: ```typescript const bigNum = CardanoWASM.BigNum.from_str('1000000000000') console.log('Big number:', bigNum.to_str()) // Arithmetic operations const sum = bigNum.checked_add(CardanoWASM.BigNum.from_str('500000')) const product = bigNum.checked_mul(CardanoWASM.BigNum.from_str('2')) ``` ### Transaction and TransactionBuilder Transaction construction: ```typescript // Create transaction builder const txBuilderCfg = CardanoWASM.TransactionBuilderConfigBuilder.new() .fee_algo(CardanoWASM.LinearFee.new( CardanoWASM.BigNum.from_str('44'), CardanoWASM.BigNum.from_str('155381') )) .pool_deposit(CardanoWASM.BigNum.from_str('500000000')) .key_deposit(CardanoWASM.BigNum.from_str('2000000')) .max_value_size(5000) .max_tx_size(16384) .coins_per_utxo_size(CardanoWASM.BigNum.from_str('4310')) .build() const txBuilder = CardanoWASM.TransactionBuilder.new(txBuilderCfg) // Add input txBuilder.add_input( fromAddress, CardanoWASM.TransactionInput.new(txHash, outputIndex), inputValue ) // Add output txBuilder.add_output( CardanoWASM.TransactionOutput.new(toAddress, outputValue) ) // Build transaction const txBody = txBuilder.build() const tx = CardanoWASM.Transaction.new( txBody, CardanoWASM.TransactionWitnessSet.new() ) ``` ### PlutusData Plutus data construction: ```typescript // Create integer datum const intDatum = CardanoWASM.PlutusData.new_integer( CardanoWASM.BigInt.from_str('42') ) // Create bytes datum const bytesDatum = CardanoWASM.PlutusData.new_bytes( new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]) ) // Create constructor datum const constrDatum = CardanoWASM.PlutusData.new_constr_plutus_data( CardanoWASM.ConstrPlutusData.new( CardanoWASM.BigNum.from_str('0'), CardanoWASM.PlutusList.new() ) ) // Create map datum const mapDatum = CardanoWASM.PlutusData.new_map( CardanoWASM.PlutusMap.new() ) // Create list datum const listDatum = CardanoWASM.PlutusData.new_list( CardanoWASM.PlutusList.new() ) ``` ### NativeScript Native script construction: ```typescript // Signature script const sigScript = CardanoWASM.NativeScript.new_script_pubkey( CardanoWASM.ScriptPubkey.new(keyHash) ) // Time lock scripts const timeLockAfter = CardanoWASM.NativeScript.new_timelock_start( CardanoWASM.TimelockStart.new(CardanoWASM.BigNum.from_str('1000000')) ) const timeLockBefore = CardanoWASM.NativeScript.new_timelock_expiry( CardanoWASM.TimelockExpiry.new(CardanoWASM.BigNum.from_str('2000000')) ) // Multi-sig scripts const scripts = CardanoWASM.NativeScripts.new() scripts.add(sigScript) scripts.add(timeLockAfter) const allScript = CardanoWASM.NativeScript.new_script_all( CardanoWASM.ScriptAll.new(scripts) ) const anyScript = CardanoWASM.NativeScript.new_script_any( CardanoWASM.ScriptAny.new(scripts) ) const nOfKScript = CardanoWASM.NativeScript.new_script_n_of_k( CardanoWASM.ScriptNOfK.new(2, scripts) // 2 of 3 ) ``` ### PlutusScript Plutus script handling: ```typescript // Create from hex with version const plutusScript = CardanoWASM.PlutusScript.from_hex_with_version( scriptHex, CardanoWASM.Language.new_plutus_v3() ) // Create from bytes const plutusScript = CardanoWASM.PlutusScript.from_bytes(scriptBytes) // Get hash const scriptHash = plutusScript.hash() console.log('Script hash:', scriptHash.to_hex()) ``` ### Certificates Certificate construction: ```typescript // Stake registration const stakeReg = CardanoWASM.Certificate.new_stake_registration( CardanoWASM.StakeRegistration.new( CardanoWASM.Credential.from_keyhash(stakeKeyHash) ) ) // Stake delegation const stakeDel = CardanoWASM.Certificate.new_stake_delegation( CardanoWASM.StakeDelegation.new( CardanoWASM.Credential.from_keyhash(stakeKeyHash), poolKeyHash ) ) // Pool registration const poolParams = CardanoWASM.PoolParams.new( poolKeyHash, vrfKeyHash, pledge, cost, margin, rewardAccount, poolOwners, relays, poolMetadata ) const poolReg = CardanoWASM.Certificate.new_pool_registration( CardanoWASM.PoolRegistration.new(poolParams) ) // Pool retirement const poolRet = CardanoWASM.Certificate.new_pool_retirement( CardanoWASM.PoolRetirement.new(poolKeyHash, epoch) ) ``` ### Metadata Transaction metadata construction: ```typescript // Create general metadata const generalMetadata = CardanoWASM.GeneralTransactionMetadata.new() // Add text metadata const textMetadatum = CardanoWASM.TransactionMetadatum.new_text('Hello World') generalMetadata.insert( CardanoWASM.BigNum.from_str('674'), textMetadatum ) // Add map metadata const metadataMap = CardanoWASM.MetadataMap.new() metadataMap.insert_str('key', 'value') const mapMetadatum = CardanoWASM.TransactionMetadatum.new_map(metadataMap) generalMetadata.insert( CardanoWASM.BigNum.from_str('721'), mapMetadatum ) // Create auxiliary data const auxData = CardanoWASM.AuxiliaryData.new() auxData.set_metadata(generalMetadata) ``` ### Withdrawals Reward withdrawals: ```typescript const withdrawals = CardanoWASM.Withdrawals.new() withdrawals.insert( CardanoWASM.RewardAddress.from_bech32('stake_test1...'), CardanoWASM.BigNum.from_str('5000000') // 5 ADA ) // Add to transaction txBuilder.set_withdrawals(withdrawals) ``` ## Enums and Constants ### NetworkId ```typescript const testnet = CardanoWASM.NetworkId.testnet() const mainnet = CardanoWASM.NetworkId.mainnet() ``` ### Languages ```typescript const plutusV1 = CardanoWASM.Language.new_plutus_v1() const plutusV2 = CardanoWASM.Language.new_plutus_v2() const plutusV3 = CardanoWASM.Language.new_plutus_v3() ``` ### CoinSelectionStrategy ```typescript const strategy = CardanoWASM.CoinSelectionStrategyCIP2.LargestFirstMultiAsset ``` ### Certificate Types ```typescript // Available certificate kinds CardanoWASM.CertificateKind.StakeRegistration CardanoWASM.CertificateKind.StakeDeregistration CardanoWASM.CertificateKind.StakeDelegation CardanoWASM.CertificateKind.PoolRegistration CardanoWASM.CertificateKind.PoolRetirement // ... and more ``` ### Redeemer Tags ```typescript CardanoWASM.RedeemerTagKind.Spend CardanoWASM.RedeemerTagKind.Mint CardanoWASM.RedeemerTagKind.Cert CardanoWASM.RedeemerTagKind.Reward CardanoWASM.RedeemerTagKind.Vote CardanoWASM.RedeemerTagKind.VotingProposal ``` ### Plutus Data Schemas ```typescript CardanoWASM.PlutusDatumSchema.DetailedSchema CardanoWASM.PlutusDatumSchema.BasicSchema ``` ### Metadata Schemas ```typescript CardanoWASM.MetadataJsonSchema.DetailedSchema CardanoWASM.MetadataJsonSchema.BasicSchema ``` ### Script Schema ```typescript CardanoWASM.ScriptSchema.Wallet CardanoWASM.ScriptSchema.Node ``` ## Complete Examples ### Basic Transaction ```typescript import { CardanoWASM } from '@hydra-sdk/cardano-wasm' function createBasicTransaction() { // Create transaction builder config const txBuilderCfg = CardanoWASM.TransactionBuilderConfigBuilder.new() .fee_algo(CardanoWASM.LinearFee.new( CardanoWASM.BigNum.from_str('44'), CardanoWASM.BigNum.from_str('155381') )) .pool_deposit(CardanoWASM.BigNum.from_str('500000000')) .key_deposit(CardanoWASM.BigNum.from_str('2000000')) .max_value_size(5000) .max_tx_size(16384) .coins_per_utxo_size(CardanoWASM.BigNum.from_str('4310')) .build() const txBuilder = CardanoWASM.TransactionBuilder.new(txBuilderCfg) // Add inputs (UTxOs) const txHash = CardanoWASM.TransactionHash.from_hex('tx_hash_hex') const txInput = CardanoWASM.TransactionInput.new(txHash, 0) const inputValue = CardanoWASM.Value.new(CardanoWASM.BigNum.from_str('10000000')) txBuilder.add_input( CardanoWASM.Address.from_bech32('addr_test1...'), txInput, inputValue ) // Add output const outputAddr = CardanoWASM.Address.from_bech32('addr_test1...') const outputValue = CardanoWASM.Value.new(CardanoWASM.BigNum.from_str('2000000')) const output = CardanoWASM.TransactionOutput.new(outputAddr, outputValue) txBuilder.add_output(output) // Set change address const changeAddr = CardanoWASM.Address.from_bech32('addr_test1...') txBuilder.add_change_if_needed(changeAddr) // Build transaction const txBody = txBuilder.build() const witnessSet = CardanoWASM.TransactionWitnessSet.new() const transaction = CardanoWASM.Transaction.new(txBody, witnessSet) return transaction } ``` ### Multi-Asset Transaction ```typescript function createMultiAssetTransaction() { const txBuilder = CardanoWASM.TransactionBuilder.new(config) // Create multi-asset value const multiAsset = CardanoWASM.MultiAsset.new() const assets = CardanoWASM.Assets.new() // Add native tokens assets.insert( CardanoWASM.AssetName.new(Buffer.from('MyToken')), CardanoWASM.BigNum.from_str('100') ) multiAsset.insert( CardanoWASM.ScriptHash.from_hex('policy_id_hex'), assets ) const outputValue = CardanoWASM.Value.new_with_assets( CardanoWASM.BigNum.from_str('2000000'), // ADA multiAsset ) const output = CardanoWASM.TransactionOutput.new(outputAddress, outputValue) txBuilder.add_output(output) return txBuilder.build() } ``` ### NFT Minting Transaction ```typescript function createNFTMintingTransaction() { const txBuilder = CardanoWASM.TransactionBuilder.new(config) // Create minting value const mint = CardanoWASM.Mint.new() const mintAssets = CardanoWASM.MintAssets.new() mintAssets.insert( CardanoWASM.AssetName.new(Buffer.from('MyNFT')), CardanoWASM.Int.new_i32(1) // Mint 1 NFT ) mint.insert( CardanoWASM.ScriptHash.from_hex('policy_id'), mintAssets ) txBuilder.set_mint(mint) // Add minting script witness const nativeScript = CardanoWASM.NativeScript.new_script_pubkey( CardanoWASM.ScriptPubkey.new(keyHash) ) const witnessSet = CardanoWASM.TransactionWitnessSet.new() const nativeScripts = CardanoWASM.NativeScripts.new() nativeScripts.add(nativeScript) witnessSet.set_native_scripts(nativeScripts) // Add NFT metadata const metadata = CardanoWASM.GeneralTransactionMetadata.new() const nftMetadata = createNFTMetadata('MyNFT', 'ipfs://...') metadata.insert(CardanoWASM.BigNum.from_str('721'), nftMetadata) const auxData = CardanoWASM.AuxiliaryData.new() auxData.set_metadata(metadata) const txBody = txBuilder.build() return CardanoWASM.Transaction.new(txBody, witnessSet, auxData) } function createNFTMetadata(name, image) { const metadataMap = CardanoWASM.MetadataMap.new() metadataMap.insert_str('name', name) metadataMap.insert_str('image', image) metadataMap.insert_str('description', 'My unique NFT') return CardanoWASM.TransactionMetadatum.new_map(metadataMap) } ``` ### Plutus Script Transaction ```typescript function createPlutusScriptTransaction() { const txBuilder = CardanoWASM.TransactionBuilder.new(config) // Add script input const scriptInput = CardanoWASM.TransactionInput.new(scriptTxHash, 0) const scriptValue = CardanoWASM.Value.new(CardanoWASM.BigNum.from_str('10000000')) txBuilder.add_input(scriptAddress, scriptInput, scriptValue) // Create redeemer const redeemer = CardanoWASM.Redeemer.new( CardanoWASM.RedeemerTag.new_spend(), CardanoWASM.BigNum.from_str('0'), // Input index CardanoWASM.PlutusData.new_empty_constr_plutus_data(CardanoWASM.BigNum.zero()), CardanoWASM.ExUnits.new( CardanoWASM.BigNum.from_str('14000000'), CardanoWASM.BigNum.from_str('10000000000') ) ) const redeemers = CardanoWASM.Redeemers.new() redeemers.add(redeemer) // Add Plutus script const plutusScript = CardanoWASM.PlutusScript.from_hex_with_version( scriptHex, CardanoWASM.Language.new_plutus_v3() ) const plutusScripts = CardanoWASM.PlutusScripts.new() plutusScripts.add(plutusScript) // Add datum const datum = CardanoWASM.PlutusData.from_hex('d87980') const datums = CardanoWASM.PlutusList.new() datums.add(datum) // Create witness set const witnessSet = CardanoWASM.TransactionWitnessSet.new() witnessSet.set_plutus_scripts(plutusScripts) witnessSet.set_plutus_data(datums) witnessSet.set_redeemers(redeemers) // Add collateral const collateralInput = CardanoWASM.TransactionInput.new(collateralTxHash, 0) const collaterals = CardanoWASM.TransactionInputs.new() collaterals.add(collateralInput) txBuilder.set_collateral(collaterals) const txBody = txBuilder.build() return CardanoWASM.Transaction.new(txBody, witnessSet) } ``` ### Staking Operations ```typescript function createStakingTransaction() { const txBuilder = CardanoWASM.TransactionBuilder.new(config) // Create stake registration certificate const stakeCredential = CardanoWASM.Credential.from_keyhash(stakeKeyHash) const stakeRegistration = CardanoWASM.StakeRegistration.new(stakeCredential) const regCert = CardanoWASM.Certificate.new_stake_registration(stakeRegistration) // Create stake delegation certificate const stakeDelegation = CardanoWASM.StakeDelegation.new( stakeCredential, poolKeyHash ) const delCert = CardanoWASM.Certificate.new_stake_delegation(stakeDelegation) // Add certificates const certs = CardanoWASM.Certificates.new() certs.add(regCert) certs.add(delCert) txBuilder.set_certs(certs) // Add key deposit for registration const keyDeposit = CardanoWASM.BigNum.from_str('2000000') return txBuilder.build() } function createRewardWithdrawal() { const txBuilder = CardanoWASM.TransactionBuilder.new(config) // Create withdrawals const withdrawals = CardanoWASM.Withdrawals.new() const rewardAddress = CardanoWASM.RewardAddress.from_bech32('stake_test1...') const rewardAmount = CardanoWASM.BigNum.from_str('5000000') // 5 ADA withdrawals.insert(rewardAddress, rewardAmount) txBuilder.set_withdrawals(withdrawals) return txBuilder.build() } ``` ### Advanced Metadata ```typescript function createAdvancedMetadata() { const generalMetadata = CardanoWASM.GeneralTransactionMetadata.new() // CIP-20 message metadata const messageMap = CardanoWASM.MetadataMap.new() const messageList = CardanoWASM.MetadataList.new() messageList.add(CardanoWASM.TransactionMetadatum.new_text('Hello')) messageList.add(CardanoWASM.TransactionMetadatum.new_text('Cardano')) messageList.add(CardanoWASM.TransactionMetadatum.new_text('Community')) messageMap.insert( CardanoWASM.TransactionMetadatum.new_text('msg'), CardanoWASM.TransactionMetadatum.new_list(messageList) ) generalMetadata.insert( CardanoWASM.BigNum.from_str('674'), CardanoWASM.TransactionMetadatum.new_map(messageMap) ) // CIP-25 NFT metadata const nftMap = CardanoWASM.MetadataMap.new() const policyMap = CardanoWASM.MetadataMap.new() const assetMap = CardanoWASM.MetadataMap.new() assetMap.insert_str('name', 'My Amazing NFT') assetMap.insert_str('description', 'This is a unique digital collectible') assetMap.insert_str('image', 'ipfs://QmHash...') const attributesList = CardanoWASM.MetadataList.new() const attribute1 = CardanoWASM.MetadataMap.new() attribute1.insert_str('trait_type', 'Color') attribute1.insert_str('value', 'Blue') attributesList.add(CardanoWASM.TransactionMetadatum.new_map(attribute1)) assetMap.insert( CardanoWASM.TransactionMetadatum.new_text('attributes'), CardanoWASM.TransactionMetadatum.new_list(attributesList) ) policyMap.insert( CardanoWASM.TransactionMetadatum.new_text('AssetName'), CardanoWASM.TransactionMetadatum.new_map(assetMap) ) nftMap.insert( CardanoWASM.TransactionMetadatum.new_text('policy_id'), CardanoWASM.TransactionMetadatum.new_map(policyMap) ) generalMetadata.insert( CardanoWASM.BigNum.from_str('721'), CardanoWASM.TransactionMetadatum.new_map(nftMap) ) return generalMetadata } ``` ## Error Handling ```typescript function handleCardanoWASMErrors() { try { // WASM operations that might fail const bigNum = CardanoWASM.BigNum.from_str('invalid_number') } catch (error) { if (error.message.includes('BigNum')) { console.error('BigNum parsing error:', error.message) // Handle BigNum specific errors } else { console.error('General WASM error:', error) } } // Safe operations with validation function safeBigNumFromStr(str) { try { return CardanoWASM.BigNum.from_str(str) } catch (error) { console.error('Invalid BigNum string:', str) return CardanoWASM.BigNum.zero() } } function safeAddressFromBech32(bech32) { try { return CardanoWASM.Address.from_bech32(bech32) } catch (error) { console.error('Invalid address:', bech32) return null } } } ``` ## Best Practices 1. **Memory Management**: WASM objects should be properly disposed when no longer needed 2. **Error Handling**: Always wrap WASM calls in try-catch blocks 3. **Validation**: Validate inputs before passing to WASM functions 4. **Big Numbers**: Use BigNum for all monetary values and large integers 5. **Serialization**: Use proper CBOR encoding/decoding methods 6. **Protocol Parameters**: Keep protocol parameters up to date 7. **Script Validation**: Validate Plutus scripts before transaction submission 8. **Metadata Standards**: Follow established metadata standards (CIP-25, CIP-20, etc.) # @hydra-sdk/core - Utilities > Hydra SDK provides a comprehensive collection of utility functions for working with Cardano blockchain data, WASM operations, and common development tasks. ## Overview Utilities are organized into multiple categories: - **[Keys Utilities](https://hydrasdk.com/#keys-utilities)** - Create and manage cryptographic keys - **[Data Parsing](https://hydrasdk.com/#data-parsing)** - Convert between data formats - **[Datum Utilities](https://hydrasdk.com/#datum-utilities)** - Work with Plutus data structures - **[Redeemer Utilities](https://hydrasdk.com/#redeemer-utilities)** - Build Plutus script redeemers - **[Validation Utilities](https://hydrasdk.com/#validation-utilities)** - Validate addresses and transaction outputs - **[Policy Utilities](https://hydrasdk.com/#policy-utilities)** - Manage minting policies - **[Metadata Utilities](https://hydrasdk.com/#metadata-utilities)** - Convert transaction metadata - **[Time Utilities](https://hydrasdk.com/#time-utilities)** - Calculate slots and time - **[Provider Utilities](https://hydrasdk.com/#provider-utilities)** - Abstract blockchain data providers - **[Cardano WASM Utilities](https://hydrasdk.com/#cardano-wasm-utilities)** - Low-level blockchain operations --- ## Import All utilities are exported as namespaced modules for better organization: ```typescript import { Serializer, Deserializer, Resolver, Converter, BuildKeys, ParserUtils, TimeUtils, DatumUtils, RedeemerUtils, ValidationUtils, PolicyUtils, MetadataUtils, ProviderUtils, CostModels, KeysUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core' ``` --- ## Keys Utilities :badge[1.1.5+]{style="transform: translateY(-2px)" variant="success"} ### **`KeysUtils.cardanoCliKeygen() → { sk: CardanoCLiSkey; vk: CardanoCLiVkey }`** Generate an ed25519 key pair compatible with Cardano CLI. Description: Generate signing and verification keys compatible with Cardano CLI format. **Example:** ```typescript import { KeysUtils } from '@hydra-sdk/core' const cardanoKeys = KeysUtils.cardanoCliKeygen() console.log({ skey: cardanoKeys.sk.cborHex, // PaymentSigningKeyShelley_ed25519 vkey: cardanoKeys.vk.cborHex // PaymentVerificationKeyShelley_ed25519 }) ``` **Returns:** `{ sk: CardanoCLiSkey; vk: CardanoCLiVkey }` ```typescript { sk: CardanoCLiSkey, vk: CardanoCLiVkey, } ``` --- ### **`KeysUtils.hydraCliKeygen() → { sk: HydraCliSkey; vk: HydraCliVkey }`** Generate an ed25519 key pair compatible with Hydra. Description: Generate signing and verification keys compatible with Hydra CLI format. **Example:** ```typescript import { KeysUtils } from '@hydra-sdk/core' const hydraKeys = KeysUtils.hydraCliKeygen() console.log({ skey: hydraKeys.sk.cborHex, // HydraSigningKey_ed25519 vkey: hydraKeys.vk.cborHex // HydraVerificationKey_ed25519 }) ``` **Returns:** `{ sk: HydraCliSkey; vk: HydraCliVkey }` ```typescript { sk: HydraCliSkey, vk: HydraCliVkey, } ``` --- ### **`KeysUtils.genVkey(skey: CardanoCLiSkey | HydraCliSkey) → CardanoCLiVkey | HydraCliVkey`** Generate a verification key from a signing key. **Parameters:** - `skey` (CardanoCLiSkey | HydraCliSkey): Source signing key **Example:** ```typescript import { KeysUtils } from '@hydra-sdk/core' const skey = KeysUtils.cardanoCliKeygen().sk const vkey = KeysUtils.genVkey(skey) console.log('Verification Key:', vkey.cborHex) ``` **Returns:** `CardanoCLiVkey | HydraCliVkey` Corresponding verification key --- ### **`KeysUtils.mnemonicToCliKey(mnemonic: string[], accountIndex?: number, keyIndex?: number)`** **→ `{ sk: CardanoCLiSkey; vk: CardanoCLiVkey }`** :badge[1.1.6+]{variant="success"} Convert a BIP-39 mnemonic phrase to Cardano CLI compatible key pair. Description: Derives payment keys from a mnemonic phrase using BIP-44 derivation path (m/1852'/1815'/account'/0/keyIndex). This allows you to generate keys that are compatible with Cardano wallets and CLI tools. **Parameters:** - `mnemonic` (string [] ): BIP-39 mnemonic words (12 or 24 words) - `accountIndex` (number, optional): Account index in derivation path (default: 0) - `keyIndex` (number, optional): Key index in derivation path (default: 0) **Example:** ```typescript import { KeysUtils, AppWallet } from '@hydra-sdk/core' // Generate a random mnemonic const mnemonic = AppWallet.brew() // Convert to CLI keys with default indexes const keys = KeysUtils.mnemonicToCliKey(mnemonic) console.log({ skey: keys.sk.cborHex, vkey: keys.vk.cborHex }) // Use specific account index (for multiple accounts) const account1Keys = KeysUtils.mnemonicToCliKey(mnemonic, 1) // Use specific key index (for multiple addresses) const addressKeys = KeysUtils.mnemonicToCliKey(mnemonic, 0, 5) ``` **Returns:** `{ sk: CardanoCLiSkey; vk: CardanoCLiVkey }` ```typescript { sk: CardanoCLiSkey, // PaymentSigningKeyShelley_ed25519 vk: CardanoCLiVkey // PaymentVerificationKeyShelley_ed25519 } ``` **Important Notes:** - Uses BIP-44 derivation path: `m/1852'/1815'/accountIndex'/0/keyIndex` - Different `accountIndex` values create different accounts from the same mnemonic - Different `keyIndex` values create different payment addresses for the same account - Keys are deterministic - same mnemonic + indexes always produce the same keys - Compatible with Cardano CLI and most Cardano wallets --- ## Data Parsing ### **`ParserUtils.bytesToHex(bytes: ArrayBuffer | Uint8Array | Buffer) → string`** Convert bytes to hex string. **Parameters:** - `bytes` (ArrayBuffer | Uint8Array | Buffer): Bytes data to convert **Example:** ```typescript import { ParserUtils } from '@hydra-sdk/core' const bytes = new Uint8Array([72, 101, 108, 108, 111]) const hex = ParserUtils.bytesToHex(bytes) console.log(hex) // "48656c6c6f" ``` **Returns:** `string` Hex string --- ### **`ParserUtils.hexToBytes(hex: string) → Buffer`** Convert hex string to bytes. **Parameters:** - `hex` (string): Hex string to convert **Example:** ```typescript import { ParserUtils } from '@hydra-sdk/core' const bytes = ParserUtils.hexToBytes('48656c6c6f') console.log(bytes) // ``` **Returns:** `Buffer` Bytes data --- ### **`ParserUtils.stringToHex(str: string) → string`** Convert UTF-8 string to hex. **Parameters:** - `str` (string): UTF-8 string **Example:** ```typescript import { ParserUtils } from '@hydra-sdk/core' const hex = ParserUtils.stringToHex('Hello World') console.log(hex) // "48656c6c6f20576f726c64" ``` **Returns:** `string` Hex string --- ### **`ParserUtils.hexToString(hex: string) → string`** Convert hex string to UTF-8. **Parameters:** - `hex` (string): Hex string to convert **Example:** ```typescript import { ParserUtils } from '@hydra-sdk/core' const str = ParserUtils.hexToString('48656c6c6f20576f726c64') console.log(str) // "Hello World" ``` **Returns:** `string` UTF-8 string --- ### **`ParserUtils.toBytes(strOrHex: string) → Uint8Array`** Convert string or hex to bytes. **Parameters:** - `strOrHex` (string): String or hex string **Example:** ```typescript import { ParserUtils } from '@hydra-sdk/core' const bytes1 = ParserUtils.toBytes('Hello World') const bytes2 = ParserUtils.toBytes('48656c6c6f') console.log(bytes1, bytes2) ``` **Returns:** `Uint8Array` Bytes data --- ### **`ParserUtils.fromUTF8(str: string) → string`** Convert UTF-8 string to hex (alias). **Parameters:** - `str` (string): UTF-8 string **Example:** ```typescript import { ParserUtils } from '@hydra-sdk/core' const hex = ParserUtils.fromUTF8('Hello') console.log(hex) // "48656c6c6f" ``` **Returns:** `string` Hex string --- ### **`ParserUtils.toUTF8(hex: string) → string`** Convert hex string to UTF-8 (alias). **Parameters:** - `hex` (string): Hex string **Example:** ```typescript import { ParserUtils } from '@hydra-sdk/core' const str = ParserUtils.toUTF8('48656c6c6f') console.log(str) // "Hello" ``` **Returns:** `string` UTF-8 string --- ## Datum Utilities ### **`DatumUtils.mkInt(n: string | number | bigint) → PlutusData`** Create an integer datum. **Parameters:** - `n` (string | number | bigint): Integer value **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const intDatum = DatumUtils.mkInt(42) const bigIntDatum = DatumUtils.mkInt(9007199254740991n) console.log(intDatum.to_hex()) ``` **Returns:** `PlutusData` Integer datum --- ### **`DatumUtils.mkBytes(hex: string) → PlutusData`** Create a bytes datum from hex string. **Parameters:** - `hex` (string): Hex string of bytes data **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const bytesDatum = DatumUtils.mkBytes('deadbeef') console.log(bytesDatum.to_hex()) ``` **Returns:** `PlutusData` Bytes datum --- ### **`DatumUtils.mkConstr(alt: number, fields: PlutusData[]) → PlutusData`** Create a constructor datum. **Parameters:** - `alt` (number): Constructor index (0-based) - `fields` (PlutusData [] ): Array of Plutus data fields **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const constrDatum = DatumUtils.mkConstr(0, [ DatumUtils.mkInt(42), DatumUtils.mkBytes('deadbeef') ]) console.log(constrDatum.to_hex()) ``` **Returns:** `PlutusData` Constructor datum --- ### **`DatumUtils.mkMap(entries: Array<[PlutusData, PlutusMapValues]>) → PlutusData`** Create a map datum. **Parameters:** - `entries` (Array< [PlutusData, PlutusMapValues] >): Array of key-value pairs **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const mapDatum = DatumUtils.mkMap([ [DatumUtils.mkInt(1), DatumUtils.mkBytes('value1')], [DatumUtils.mkInt(2), DatumUtils.mkBytes('value2')] ]) console.log(mapDatum.to_hex()) ``` **Returns:** `PlutusData` Map datum --- ### **`DatumUtils.mkList(elements: PlutusData[]) → PlutusData`** :badge{text="New in v1.3.2" type="tip"} Create a list datum from an array of Plutus data elements. **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const listDatum = DatumUtils.mkList([DatumUtils.mkInt(1), DatumUtils.mkInt(2)]) console.log(listDatum.to_hex()) ``` **Returns:** `PlutusData` --- ### **`DatumUtils.mkBool(value: boolean) → PlutusData`** :badge{text="New in v1.4.0" type="tip"} Create a boolean datum. Encoded as `Constr(0, [])` for `false` and `Constr(1, [])` for `true`. **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const trueDatum = DatumUtils.mkBool(true) console.log(trueDatum.to_hex()) ``` **Returns:** `PlutusData` --- ### **`DatumUtils.mkOption(value?: PlutusData | null) → PlutusData`** :badge{text="New in v1.4.0" type="tip"} Create an optional datum. `Some` is `Constr(0, [value])`; `None` (no/`null` value) is `Constr(1, [])`. **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const some = DatumUtils.mkOption(DatumUtils.mkInt(42)) const none = DatumUtils.mkOption() ``` **Returns:** `PlutusData` --- ### **`DatumUtils.mkBytesList(items: Array) → PlutusData`** :badge{text="New in v1.4.0" type="tip"} Create a list datum from an array of byte strings (hex) or `Uint8Array` values. **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const bytesList = DatumUtils.mkBytesList(['deadbeef', new Uint8Array([1, 2, 3])]) ``` **Returns:** `PlutusData` --- ### **`DatumUtils.mkIntList(items: Array) → PlutusData`** :badge{text="New in v1.4.0" type="tip"} Create a list datum from an array of integer values. **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const intList = DatumUtils.mkIntList([1, 2, 9007199254740991n]) ``` **Returns:** `PlutusData` --- ### **`DatumUtils.mkOutputRef(ref: { txHash: string; index: number | bigint }) → PlutusData`** :badge{text="New in v1.4.0" type="tip"} Create an output reference datum, encoded as `Constr(0, [Bytes(txHash), Int(index)])`. **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const outputRef = DatumUtils.mkOutputRef({ txHash: '1d2e5b97f1cad7bea2b06144abc4974012fa786c9e7e5faddd03243b967a03f6', index: 0 }) ``` **Returns:** `PlutusData` --- ### **`DatumUtils.mkAddress(bech32: string) → PlutusData`** :badge{text="New in v1.4.0" type="tip"} Encode a bech32 Cardano address into its Plutus data representation. **Example:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const addrDatum = DatumUtils.mkAddress('addr1qx2fxv2umyhttkxyxp8x0...') ``` **Returns:** `PlutusData` --- ### **`DatumUtils.parseAddress(data: PlutusData, networkId?: number) → string`** :badge{text="New in v1.4.0" type="tip"} Decode Plutus address data back into a bech32 address string. **Parameters:** - `data` (PlutusData): Plutus address data - `networkId` (number, optional): Network ID for the resulting address (default: MAINNET) **Example:** ```typescript import { DatumUtils, NETWORK_ID } from '@hydra-sdk/core' const bech32 = DatumUtils.parseAddress(addrDatum, NETWORK_ID.PREPROD) ``` **Returns:** `string` Bech32 address --- ## Redeemer Utilities :badge[1.4.0+]{style="transform: translateY(-2px)" variant="success"} Build Plutus script redeemers. Import: `import { RedeemerUtils } from '@hydra-sdk/core'`. ### **`RedeemerUtils.mkRedeemer(data: PlutusData, options?: BuildRedeemerOptions) → Redeemer`** Wrap Plutus data into a `Redeemer`. **Parameters:** - `data` (PlutusData): Redeemer data - `options`(BuildRedeemerOptions, optional): - `tag` ('spend' | 'mint' | 'reward' | 'cert' | 'vote' | 'voting\_proposal', case-insensitive): Redeemer tag. Default: `'spend'` - `index` (string | number | bigint): Redeemer index. Default: `0` - `exUnits` (`{ mem, steps }`): Execution unit budget. Default: `RedeemerUtils.DEFAULT_EX_UNITS` **Example:** ```typescript import { RedeemerUtils, DatumUtils } from '@hydra-sdk/core' const data = DatumUtils.mkConstr(1, []) const redeemer = RedeemerUtils.mkRedeemer(data, { tag: 'spend', index: 0 }) ``` **Returns:** `Redeemer` --- ### **`RedeemerUtils.mkSpendRedeemer(data: PlutusData, options?) → Redeemer`** Build a redeemer with the tag preset to `spend`. **Example:** ```typescript import { RedeemerUtils, DatumUtils } from '@hydra-sdk/core' const redeemer = RedeemerUtils.mkSpendRedeemer(DatumUtils.mkConstr(0, [])) ``` **Returns:** `Redeemer` --- ### **`RedeemerUtils.mkMintRedeemer(data: PlutusData, options?) → Redeemer`** Build a redeemer with the tag preset to `mint`. **Example:** ```typescript import { RedeemerUtils, DatumUtils } from '@hydra-sdk/core' const redeemer = RedeemerUtils.mkMintRedeemer(DatumUtils.mkConstr(0, [])) ``` **Returns:** `Redeemer` --- ### **`RedeemerUtils.mkUnitRedeemer(options?) → Redeemer`** Build the Unit redeemer `Constr(0, [])` — the "no argument" redeemer. **Example:** ```typescript import { RedeemerUtils } from '@hydra-sdk/core' const redeemer = RedeemerUtils.mkUnitRedeemer() ``` **Returns:** `Redeemer` --- ### **`RedeemerUtils.mkExUnits({ mem, steps }) → ExUnits`** Build an `ExUnits` execution budget. `RedeemerUtils.DEFAULT_EX_UNITS` is `{ mem: '5000000', steps: '2000000000' }` (a placeholder budget — evaluate scripts for production). **Example:** ```typescript import { RedeemerUtils } from '@hydra-sdk/core' const exUnits = RedeemerUtils.mkExUnits({ mem: '5000000', steps: '2000000000' }) ``` **Returns:** `ExUnits` --- ## Validation Utilities :badge[1.4.0+]{style="transform: translateY(-2px)" variant="success"} ### **`ValidationUtils.isValidTxOutput(output: TxOutput) → boolean`** Check whether a transaction output is valid. Import: `import { ValidationUtils } from '@hydra-sdk/core'`. **Parameters:** - `output` (TxOutput): Transaction output to verify **Example:** ```typescript import { ValidationUtils } from '@hydra-sdk/core' const isValid = ValidationUtils.isValidTxOutput(txOutput) console.log('Output valid:', isValid) ``` **Returns:** `boolean` --- ## Policy Utilities ### **`PolicyUtils.buildPolicyScriptFromPubkey(pubkeyScript: PubkeyScript) → string`** Build a policy script from public key. **Parameters:** - `pubkeyScript`(PubkeyScript): - `type`: 'sig' - `keyHash` (string): Public key hash **Example:** ```typescript import { PolicyUtils } from '@hydra-sdk/core' const policyScript = PolicyUtils.buildPolicyScriptFromPubkey({ type: 'sig', keyHash: 'abc123def456...' }) console.log('Policy Script:', policyScript) ``` **Returns:** `string` Native script in JSON format --- ### **`PolicyUtils.buildMintingPolicyScriptFromAddress(addressBech32: string) → string`** Build a minting policy script from an address. **Parameters:** - `addressBech32` (string): Cardano address (bech32) **Example:** ```typescript import { PolicyUtils } from '@hydra-sdk/core' const mintingScript = PolicyUtils.buildMintingPolicyScriptFromAddress( 'addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp' ) console.log('Minting Script:', mintingScript) ``` **Returns:** `string` Script in JSON format --- ### **`PolicyUtils.buildMintingPolicyScriptFromKeyHash(keyHash: string) → string`** Build a minting policy script from key hash. **Parameters:** - `keyHash` (string): Public key hash (hex) **Example:** ```typescript import { PolicyUtils } from '@hydra-sdk/core' const mintingScript = PolicyUtils.buildMintingPolicyScriptFromKeyHash('abc123...') console.log('Minting Script:', mintingScript) ``` **Returns:** `string` Script in JSON format --- ### **`PolicyUtils.policyIdFromNativeScript(nativeScript: string) → string`** Extract Policy ID from native script. **Parameters:** - `nativeScript` (string): Native script in JSON string format **Example:** ```typescript import { PolicyUtils } from '@hydra-sdk/core' const policyId = PolicyUtils.policyIdFromNativeScript(policyScript) console.log('Policy ID:', policyId) ``` **Returns:** `string` Policy ID (hex hash) --- ## Metadata Utilities ### **`MetadataUtils.metadataObjToMetadatum(metadata: any) → TransactionMetadatum`** Convert various data types to Cardano transaction metadata. **Parameters:** - `metadata` (any): Data to convert (string, number, bigint, Uint8Array, array, object, Map) **Example:** ```typescript import { MetadataUtils } from '@hydra-sdk/core' // Convert string const stringMetadata = MetadataUtils.metadataObjToMetadatum('Hello World') // Convert number const numberMetadata = MetadataUtils.metadataObjToMetadatum(42) // Convert bigint const bigintMetadata = MetadataUtils.metadataObjToMetadatum(9007199254740991n) // Convert bytes const bytesMetadata = MetadataUtils.metadataObjToMetadatum( new Uint8Array([1, 2, 3, 4]) ) // Convert array const arrayMetadata = MetadataUtils.metadataObjToMetadatum([ 'item1', 42, new Uint8Array([5, 6]) ]) // Convert object to map const objectMetadata = MetadataUtils.metadataObjToMetadatum({ name: 'NFT Name', image: 'ipfs://...', attributes: ['trait1', 'trait2'] }) // Convert Map const mapMetadata = MetadataUtils.metadataObjToMetadatum( new Map([ ['key1', 'value1'], ['key2', 42] ]) ) ``` **Returns:** `TransactionMetadatum` Cardano formatted metadata **Important Notes:** - Maximum metadata `bytes` size: **64 bytes** - Maximum metadata `text` size: **64 characters** - Arrays and Maps are processed recursively, pay attention to total metadata size --- ## Time Utilities ### **`TimeUtils.slotToBeginUnixTime(slot: number, slotConfig: SlotConfig) → number`** Convert slot to Unix timestamp. **Parameters:** - `slot` (number): Slot number - `slotConfig` (SlotConfig): Slot configuration (PREPROD, MAINNET, etc.) **Example:** ```typescript import { TimeUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core' const timestamp = TimeUtils.slotToBeginUnixTime( 1000, SLOT_CONFIG_NETWORK.PREPROD ) const date = new Date(timestamp * 1000) console.log('Slot start time:', date.toLocaleString()) ``` **Returns:** `number` Unix timestamp (seconds) --- ### **`TimeUtils.unixTimeToEnclosingSlot(unixTime: number, slotConfig: SlotConfig) → number`** Convert Unix timestamp to slot. **Parameters:** - `unixTime` (number): Unix timestamp (seconds) - `slotConfig` (SlotConfig): Slot configuration **Example:** ```typescript import { TimeUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core' const slot = TimeUtils.unixTimeToEnclosingSlot( Math.floor(Date.now() / 1000), SLOT_CONFIG_NETWORK.PREPROD ) console.log('Current slot:', slot) ``` **Returns:** `number` Slot number --- ### **`TimeUtils.resolveSlotNo(network: Network, milliseconds?: number) → string`** Determine slot number for a network at a given time. **Parameters:** - `network` (Network): Target network (`'MAINNET'` | `'PREPROD'` | `'PREVIEW'`) - `milliseconds` (number, optional): Timestamp in milliseconds (default: `Date.now()`) **Example:** ```typescript import { TimeUtils } from '@hydra-sdk/core' const slotNo = TimeUtils.resolveSlotNo('PREPROD') console.log('Current slot:', slotNo) ``` **Returns:** `string` Slot number as string --- ### **`TimeUtils.resolveEpochNo(network: Network, milliseconds?: number) → number`** Determine epoch number for a network at a given time. **Parameters:** - `network` (Network): Target network (`'MAINNET'` | `'PREPROD'` | `'PREVIEW'`) - `milliseconds` (number, optional): Timestamp in milliseconds (default: `Date.now()`) **Example:** ```typescript import { TimeUtils } from '@hydra-sdk/core' const epochNo = TimeUtils.resolveEpochNo('PREPROD') console.log('Current epoch:', epochNo) ``` **Returns:** `number` Epoch number --- ### **`TimeUtils.buildHydraSlotConfig(startTimestamp: number, options?: object) → SlotConfig`** Build Hydra slot configuration anchored at a Head start timestamp. **Parameters:** - `startTimestamp` (number): Head start timestamp in milliseconds (required) - `options` (object, optional): Overrides for `zeroSlot`, `slotLength`, `startEpoch`, and `epochLength` **Example:** ```typescript import { TimeUtils } from '@hydra-sdk/core' const slotConfig = TimeUtils.buildHydraSlotConfig(Date.now()) console.log('Hydra Slot Config:', { zeroTime: slotConfig.zeroTime, zeroSlot: slotConfig.zeroSlot, slotLength: slotConfig.slotLength }) ``` **Returns:** `SlotConfig` Slot configuration --- ## Provider Utilities ### **`ProviderUtils.BlockfrostProvider(config: BlockfrostProviderConfig) → BlockfrostProvider`** Create a Blockfrost provider to query and submit transactions to the Cardano blockchain. Description: BlockfrostProvider is a wallet provider that connects to Blockfrost API for fetching UTxOs and submitting transactions. It extends BaseWalletProvider and provides both fetcher and submitter functionality. **Parameters:** - `config`(BlockfrostProviderConfig): - `apiKey` (string): Your Blockfrost Project ID from [Blockfrost Dashboard](https://blockfrost.io/){rel=""nofollow""} - `network` (BlockfrostSupportedNetworks): Network to connect to ('mainnet', 'preprod', or 'preview') - `apiVersion` (number, optional): Blockfrost API version (default: 0) - `baseURL` (string, optional): Override the default Blockfrost endpoint URL. Useful for Blockfrost-compatible APIs such as Demeter. :badge{text="New in v1.3.2" type="tip"} - `cachingOptions`(object, optional): Caching configuration - `enabled` (boolean): Enable response caching (default: false) - `maxSize` (number): Maximum cache items (default: 100) - `ttl` (number): Time to live in milliseconds (default: 300000 - 5 minutes) **Properties:** - `fetcher`(IFetcher): Interface for fetching blockchain data - `fetchAddressUTxOs(address: string, asset?: string): Promise` - Fetch UTxOs for an address, optionally filtered by asset - `submitter`(ISubmitter): Interface for submitting transactions - `submitTx(txHex: string): Promise` - Submit a signed transaction and return transaction hash **Example:** ```typescript import { ProviderUtils } from '@hydra-sdk/core' // Basic configuration const provider = new ProviderUtils.BlockfrostProvider({ apiKey: 'preprodXXXXXXXXXXXXXXXXXXXX', network: 'preprod' }) // With API version and caching const providerWithCache = new ProviderUtils.BlockfrostProvider({ apiKey: 'preprodXXXXXXXXXXXXXXXXXXXX', network: 'preprod', apiVersion: 0, cachingOptions: { enabled: true, maxSize: 200, ttl: 600000 // 10 minutes } }) // Fetch all UTxOs for an address const utxos = await provider.fetcher.fetchAddressUTxOs( 'addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer...' ) // Fetch UTxOs containing a specific asset const assetUtxos = await provider.fetcher.fetchAddressUTxOs( 'addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer...', 'lovelace' // or policy_id + asset_name ) // Submit a signed transaction const txHash = await provider.submitter.submitTx(signedTxHex) console.log('Transaction submitted:', txHash) ``` **Returns:** `BlockfrostProvider` Provider instance --- ### **`ProviderUtils.OgmiosProvider(config: OgmiosProviderConfig) → OgmiosProvider`** Create an Ogmios provider to query and submit transactions using JSON-RPC 2.0 protocol. Description: OgmiosProvider is a wallet provider that connects to Ogmios API via HTTP for fetching UTxOs and submitting transactions. It extends BaseWalletProvider and uses JSON-RPC 2.0 protocol to communicate with Ogmios server. **Parameters:** - `config`(OgmiosProviderConfig): - `network` (OgmiosSupportedNetworks): Network to connect to ('mainnet', 'preprod', or 'preview') - `apiEndpoint` (string, optional): Ogmios API endpoint URL **Properties:** - `fetcher`(IFetcher): Interface for fetching blockchain data - `fetchAddressUTxOs(address: string, asset?: string): Promise` - Fetch UTxOs for an address, optionally filtered by asset - `submitter`(ISubmitter): Interface for submitting transactions - `submitTx(tx: string): Promise` - Submit a signed transaction and return transaction hash **Example:** ```typescript import { ProviderUtils } from '@hydra-sdk/core' // Connect to remote Ogmios instance const remoteProvider = new ProviderUtils.OgmiosProvider({ network: 'preprod', apiEndpoint: 'https://preprod.ogmios.cardano-rpc.hydrawallet.app' }) // Fetch all UTxOs for an address const utxos = await remoteProvider.fetcher.fetchAddressUTxOs( 'addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer...' ) // Fetch UTxOs containing a specific asset const assetUtxos = await remoteProvider.fetcher.fetchAddressUTxOs( 'addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer...', 'lovelace' // or policy_id + asset_name ) // Submit a signed transaction const txHash = await remoteProvider.submitter.submitTx(signedTxHex) console.log('Transaction submitted:', txHash) ``` **Returns:** `OgmiosProvider` Provider instance with fetcher and submitter interfaces --- ### **`ProviderUtils.DemeterProvider(config: DemeterProviderConfig) → DemeterProvider`** :badge{text="New in v1.3.2" type="tip"} Create a provider for [Demeter](https://demeter.run){rel=""nofollow""}'s Blockfrost-compatible hosted endpoints. Extends `BlockfrostProvider`, so it exposes the same `fetcher`/`submitter` surface. Authentication is embedded in the endpoint subdomain via `authToken`. **Parameters:** - `config`(DemeterProviderConfig): - `authToken` (string): Your Demeter auth token (also used as the authenticated subdomain) - `network` (DemeterSupportedNetworks): Network to connect to ('mainnet', 'preprod', or 'preview') - `apiVersion` (number, optional): Blockfrost API version (default: 0) - `cachingOptions` (object, optional): Same caching configuration as `BlockfrostProvider` **Example:** ```typescript import { ProviderUtils } from '@hydra-sdk/core' const provider = new ProviderUtils.DemeterProvider({ authToken: 'blockfrost102lx3ckhzvkjjh7677g', network: 'preprod' }) // Same fetcher / submitter interface as BlockfrostProvider const utxos = await provider.fetcher.fetchAddressUTxOs( 'addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer...' ) const txHash = await provider.submitter.submitTx(signedTxHex) ``` The endpoint is built as `https://{authToken}.cardano-{network}.blockfrost-m1.demeter.run/api/v{apiVersion ?? 0}`. **Returns:** `DemeterProvider` Provider instance with fetcher and submitter interfaces --- ## Cardano WASM Utilities ### **`Serializer.serializeAssetUnit(policyId: string, assetName: string) → string`** Build an asset unit string by concatenating a policy ID and an asset name (both hex). **Parameters:** - `policyId` (string): Policy ID (hex) - `assetName` (string): Asset name (hex) **Example:** ```typescript import { Serializer } from '@hydra-sdk/core' const unit = Serializer.serializeAssetUnit('a1b2c3...', '48656c6c6f') console.log('Asset unit:', unit) ``` **Returns:** `string` Asset unit (policyId + assetName) --- ### **`Deserializer.deserializeTx(txCborHex: string) → FixedTransaction`** Deserialize a transaction CBOR hex string into a `CardanoWASM.FixedTransaction`. **Parameters:** - `txCborHex` (string): Transaction CBOR hex string **Example:** ```typescript import { Deserializer } from '@hydra-sdk/core' const tx = Deserializer.deserializeTx(cborHex) console.log('Tx hash:', tx.transaction_hash().to_hex()) ``` **Returns:** `FixedTransaction` Parsed transaction object --- ### **`Deserializer.deserializeAmountsFromTx(cborHex: string) → Asset[]`** :badge{text="New in v1.4.0" type="tip"} Return all amounts (lovelace + native tokens) across every output of a transaction, merged by unit. **Parameters:** - `cborHex` (string): Transaction CBOR hex string **Example:** ```typescript import { Deserializer } from '@hydra-sdk/core' const amounts = Deserializer.deserializeAmountsFromTx(cborHex) console.log('Amounts:', amounts) // => [{ unit: 'lovelace', quantity: '5000000' }, ...] ``` **Returns:** `Asset[]` Array of `{ unit, quantity }` entries merged by unit --- ### **`Resolver.resolveTxHash(cborHex: string) → string`** Resolve the transaction hash from a transaction CBOR hex string. **Parameters:** - `cborHex` (string): Transaction CBOR hex string **Example:** ```typescript import { Resolver } from '@hydra-sdk/core' const txHash = Resolver.resolveTxHash(cborHex) console.log('Transaction hash:', txHash) ``` **Returns:** `string` Transaction hash (hex) --- ### **`Resolver.resolveTxBodyHash(txBody: TransactionBody) → TransactionHash`** Resolve the transaction hash from a `TransactionBody`. **Parameters:** - `txBody` (TransactionBody): Transaction body object **Example:** ```typescript import { Resolver } from '@hydra-sdk/core' const hash = Resolver.resolveTxBodyHash(txBody) console.log('Transaction hash:', hash.to_hex()) ``` **Returns:** `TransactionHash` Transaction hash object --- ### **`Converter.convertUTxOObjectToUTxO(utxoObject: UTxOObject) → UTxO[]`** Convert a snapshot-format UTxO object into an array of `UTxO` items. Use `Converter.convertUTxOToUTxOObject(utxos)` for the reverse direction, and `Converter.convertUTxOObjectToUTxOWithOptions(utxoObject, options)` to tune the datum deserialization cache. **Parameters:** - `utxoObject` (UTxOObject): Snapshot UTxO object **Example:** ```typescript import { Converter } from '@hydra-sdk/core' const utxos = Converter.convertUTxOObjectToUTxO(snapshotUtxo) console.log('UTxO count:', utxos.length) // Reverse conversion const snapshot = Converter.convertUTxOToUTxOObject(utxos) ``` **Returns:** `UTxO[]` Array of UTxOs > **Hex conversion helpers** live on `ParserUtils` (see [Data Parsing](https://hydrasdk.com/#data-parsing)) — use `ParserUtils.bytesToHex` / `ParserUtils.hexToBytes`. `Converter` deals with UTxO shapes, not raw hex. --- ### **`BuildKeys.buildKeys(privateKeyHex: string | [string, string], accountIndex: number, keyIndex?: number) → { accountKey, paymentKey, stakeKey, dRepKey? }`** Derive account, payment, stake, and dRep keys from a root private key using the BIP-44 derivation path `m/1852'/1815'/accountIndex'/role/keyIndex`. **Parameters:** - `privateKeyHex` (string | [string, string] ): Root private key hex (or an `[accountKey, stakeKey]` pair) - `accountIndex` (number): Account index - `keyIndex` (number, optional): Key index (default: 0) **Example:** ```typescript import { BuildKeys } from '@hydra-sdk/core' const { accountKey, paymentKey, stakeKey } = BuildKeys.buildKeys(rootKeyHex, 0, 0) console.log('Payment key:', paymentKey.to_hex()) ``` **Returns:** `{ accountKey, paymentKey, stakeKey, dRepKey? }` Derived `Bip32PrivateKey` values --- ### **`BuildKeys.buildBaseAddress(networkId: number, paymentKeyHash, stakeKeyHash) → BaseAddress`** Build a base address (payment + staking) from payment and stake key hashes. Companion helpers `buildEnterpriseAddress` and `buildRewardAddress` are also available. **Parameters:** - `networkId` (number): Network ID (e.g. `NETWORK_ID.PREPROD`) - `paymentKeyHash` (Ed25519KeyHash): Payment key hash - `stakeKeyHash` (Ed25519KeyHash): Stake key hash **Example:** ```typescript import { BuildKeys, NETWORK_ID } from '@hydra-sdk/core' const baseAddress = BuildKeys.buildBaseAddress( NETWORK_ID.PREPROD, paymentKeyHash, stakeKeyHash ) console.log('Base address:', baseAddress.to_address().to_bech32()) ``` **Returns:** `BaseAddress` Base address object --- ## Cost Models ### **`CostModels.buildCostModels(models: { plutusV1?: number[]; plutusV2?: number[]; plutusV3?: number[] }) → Costmdls`** Build cost models for Plutus scripts. At least one of `plutusV1` / `plutusV2` / `plutusV3` must be provided, otherwise it throws `At least one cost model must be provided`. For zero-config defaults use `CostModels.defaultCostModels`. **Parameters:** - `models` (object): Cost model lists keyed by Plutus language version **Example:** ```typescript import { CostModels } from '@hydra-sdk/core' // Ready-made defaults for all three Plutus versions const defaults = CostModels.defaultCostModels // Or build from explicit cost model lists const costModels = CostModels.buildCostModels({ plutusV3: [/* ...cost model integers... */] }) console.log('Cost Models:', costModels) ``` **Returns:** `Costmdls` Cost models object --- ## Complete Examples ### Basic Data Conversion ```typescript import { ParserUtils, DatumUtils, PolicyUtils, SLOT_CONFIG_NETWORK, TimeUtils } from '@hydra-sdk/core' // Convert data const hex = ParserUtils.stringToHex('Hello Cardano') const bytes = ParserUtils.hexToBytes(hex) console.log('Hex:', hex) console.log('Bytes:', bytes) // Work with time const currentSlot = TimeUtils.unixTimeToEnclosingSlot( Math.floor(Date.now() / 1000), SLOT_CONFIG_NETWORK.PREPROD ) const readableTime = new Date( TimeUtils.slotToBeginUnixTime(currentSlot, SLOT_CONFIG_NETWORK.PREPROD) * 1000 ) console.log('Current slot:', currentSlot) console.log('Readable time:', readableTime.toLocaleString()) // Create datum const datum = DatumUtils.mkConstr(0, [ DatumUtils.mkInt(42), DatumUtils.mkBytes(hex) ]) console.log('Datum hex:', datum.to_hex()) ``` ### Working with Keys ```typescript import { KeysUtils } from '@hydra-sdk/core' // Generate Cardano CLI keys const cardanoKeys = KeysUtils.cardanoCliKeygen() console.log({ signingKey: cardanoKeys.sk.cborHex, verificationKey: cardanoKeys.vk.cborHex }) // Generate Hydra keys const hydraKeys = KeysUtils.hydraCliKeygen() console.log({ hydraSigningKey: hydraKeys.sk.cborHex, hydraVkey: hydraKeys.vk.cborHex }) // Generate verification key from signing key const vkey = KeysUtils.genVkey(cardanoKeys.sk) console.log('Verification Key:', vkey.cborHex) ``` ### Creating Minting Policy ```typescript import { PolicyUtils, ParserUtils } from '@hydra-sdk/core' // Build policy from key hash const keyHash = '...' // Your public key hash const policyScript = PolicyUtils.buildMintingPolicyScriptFromKeyHash(keyHash) // Extract Policy ID const policyId = PolicyUtils.policyIdFromNativeScript(policyScript) console.log('Policy ID:', policyId) // Build policy from address const address = 'addr1qx2fxv2...' const addressPolicy = PolicyUtils.buildMintingPolicyScriptFromAddress(address) const addressPolicyId = PolicyUtils.policyIdFromNativeScript(addressPolicy) console.log('Address Policy ID:', addressPolicyId) ``` ### Handling Metadata ```typescript import { MetadataUtils } from '@hydra-sdk/core' // NFT metadata const nftMetadata = MetadataUtils.metadataObjToMetadatum({ name: 'My NFT', image: 'ipfs://QmXxxx...', description: 'A beautiful NFT', attributes: [ { trait_type: 'Rarity', value: 'Rare' }, { trait_type: 'Color', value: 'Blue' } ] }) // Custom metadata with numbers and bytes const customMetadata = MetadataUtils.metadataObjToMetadatum( new Map([ ['owner', 'Alice'], ['value', 1000000], ['hash', new Uint8Array([0x12, 0x34, 0x56, 0x78])] ]) ) console.log('NFT Metadata:', nftMetadata) console.log('Custom Metadata:', customMetadata) ``` ### Query Blockchain with Provider ```typescript import { ProviderUtils } from '@hydra-sdk/core' // Using Blockfrost const blockfrostProvider = new ProviderUtils.BlockfrostProvider({ apiKey: 'your-blockfrost-project-id', network: 'preprod' }) // Query UTxOs via the fetcher (there is no getUtxos/getProtocolParameters on providers) const utxos = await blockfrostProvider.fetcher.fetchAddressUTxOs(address) console.log('UTxOs:', utxos) // Submit via the submitter // const txHash = await blockfrostProvider.submitter.submitTx(signedTxHex) // Using Ogmios const ogmiosProvider = new ProviderUtils.OgmiosProvider({ network: 'preprod', apiEndpoint: 'http://localhost:1337' }) const ogmiosUtxos = await ogmiosProvider.fetcher.fetchAddressUTxOs(address) console.log('Ogmios UTxOs:', ogmiosUtxos) ``` --- ## Type Definitions All utilities come with comprehensive TypeScript definitions: ```typescript import type { SlotConfig, Network, PlutusData, ConstrPlutusData, PlutusMap, CardanoCLiSkey, CardanoCLiVkey, HydraCliSkey, HydraCliVkey, TransactionMetadatum } from '@hydra-sdk/core' ``` --- ## Related Documentation - [Core API](https://hydrasdk.com/api/core) - Wallet and authentication - [Transaction Builder API](https://hydrasdk.com/api/transaction) - Build transactions - [Bridge API](https://hydrasdk.com/api/bridge) - Hydra Layer 2 integration # Change Logs ## July 2026 — `bridge` v2.0.0 (`next` tag) `@hydra-sdk/bridge` **2.0.0** retargets the bridge from `hydra-node` v1 to the **v2 line** (verified against **2.3.0**). The bridge major version now tracks the hydra-node major version, and **1.x is end of life**. ::warning Published under the `next` dist-tag. `latest` still resolves to **1.3.2**. Install with `pnpm add @hydra-sdk/bridge@next`. :: ### 💥 Breaking — hydra-node v2 removed the commit phase v2 implements ADR-33: a head **opens directly**, with no commit phase to abort. Funds enter an open head through **incremental deposits** instead. - `commands.abort()` and `HydraCommand.GetUTxO` removed - `commands.initSync()` resolves on `HeadIsOpen`, not `HeadIsInitializing` - Tags removed: `HeadIsInitializing`, `Committed`, `HeadIsAborted`, `CommitIgnored`, `GetUTxOResponse`, `PeerHandshakeFailure` - `HeadIsOpen` is `{ headId, parties }` — it no longer carries `utxo` - `HeadIsFinalized.utxo` renamed to `finalizedUTxO` - `HydraHeadStatus` drops `Initializing` and `Final` (`Final` never existed in any released hydra-node) - `HydraHeadInfo` is a discriminated union over `Idle | Open | Closed` - `SubmitTxResponse` rebuilt from `Hydra.Chain.PostTxError` - `Greetings`, `InvalidInput` and every `ClientMessage` carry no `seq` / `timestamp` ### ✨ New - Commands `safeClose()`, `sideLoadSnapshot()`, `partialFanout()` - `submitL2Tx()` — submits over `POST /transaction` and lets the node return the verdict, instead of racing WebSocket messages against a client timeout - `pendingDeposits()` and `recoverDeposit()` for the deposit lifecycle - `getRawProtocolParameters()` exposing `costModels` / `protocolVersion` for ExUnits budgeting under PV11 - `bridge.syncedStatus` and `bridge.nodeVersion`; the bridge now fails fast on `RejectedInputBecauseUnsynced` instead of hanging until timeout ### 🐛 Found by testing against a live node The v2 types were derived from the hydra Haskell source, then checked against a running hydra-node 2.3.0. That check found five defects a source read alone had missed: - `Greetings.env` has no `signingKey` — the node's `ToJSON` deliberately omits it - `Greetings` `Maybe` fields are omitted rather than `null`, so `hydraHeadId` / `snapshotUtxo` are optional - `InvalidInput` arrives with **no `tag`**; the connectors now stamp one so the payload union stays discriminated - `POST /transaction` takes the bare tx envelope, not `{ submitL2Tx: … }` - Chain-waiting endpoints timed out client-side at 30s while the node works to its own 300s `api-transaction-timeout` — `DELETE /commits` reported failure for a recovery the node had completed ### 🧹 Housekeeping - The bridge's duplicated protocol-parameter constants are gone; it now uses `@hydra-sdk/core`, which carries the **PV11** cost models. `toProtocol()` routes through `castProtocol`, so a zero `utxoCostPerByte` — what the node reports inside a head — is preserved rather than replaced by the L1 default. See [MIGRATION-v2.md](https://github.com/Vtechcom/hydra-sdk/blob/master/packages/hydra-bridge/MIGRATION-v2.md){rel=""nofollow""} for the full upgrade path. --- ## July 2026 Release — `transaction` v1.2.0 · `core` v1.4.1 · `bridge` v1.3.2 · `cardano-wasm` v1.0.0 (Latest) The headline is **`@hydra-sdk/transaction` v1.2.0** — a memory-safety and features release for `TxBuilder`. `@hydra-sdk/cardano-wasm` also reaches its first stable **v1.0.0**. ### 🧠 Memory Safety — `@hydra-sdk/transaction` - **WASM memory leak & progressive slowdown fixed**: every `@emurgo/cardano-serialization-lib` object lives in WASM linear memory and is only reclaimed by `.free()`. CSL's `FinalizationRegistry` cleanup is non-deterministic and cannot keep up under a tight build loop, so the WASM heap grew unbounded and per-transaction build time degraded steadily. `complete()` now frees every internally-allocated CSL object deterministically after `build_tx()`. Measured result: flat WASM memory (\~0 KB/iter) and steady throughput across 10,000 builds; 280 existing tests unchanged. Caller-supplied objects (datum / redeemer / script) are never freed. - **`completeCbor(): Promise`** (new): builds the transaction and returns its CBOR hex, freeing the intermediate `Transaction` immediately — the leak-free path for high-volume workloads where you only need the serialized bytes. `complete()` still returns a live `Transaction`; the caller is responsible for calling `.free()` on it. - **`dispose()` + `[Symbol.dispose]`** (new): release all WASM memory held by a builder. Works with the `using` keyword (`using tx = new TxBuilder()`); recommended for high-volume / spike workloads instead of relying on the GC. ### 🚀 New Features — `@hydra-sdk/transaction` - **Stake certificates are now built**: `registerStake()`, `deregisterStake()`, and `delegateStake()` previously staged certificates that were silently dropped at build time — they are now applied via `set_certs`, so the built transaction actually contains the certificate(s). The stake credential is resolved from the bech32 reward (stake) address; an invalid reward address now throws instead of being ignored. `PoolRegistration` / `PoolRetirement` throw a clear "not supported yet" error. - **`totalCollateral()` is now applied** to the built transaction via `set_total_collateral` (previously the value was stored but never written). - **Optional script exUnits evaluation**: `TxBuilder` accepts an `evaluator?: IEvaluator` (plus a `txEvaluationMultiplier?` safety margin). When supplied and the transaction contains Plutus redeemers, `complete()` runs a second build pass — the draft transaction is evaluated to obtain real `exUnits`, those are written back into the redeemers (`SPEND` matched by input `txHash#index`, `MINT` by policy id), and the transaction is rebuilt so the fee is accurate. Without an evaluator (e.g. for Hydra, which has no on-chain script evaluation), behaviour is unchanged. The `IEvaluator` / `EvalAction` / `Budget` types mirror MeshJS for interoperability. *Current limits:* only `SPEND` and `MINT` budgets are remapped, and no offline evaluator is bundled yet — supply a provider-backed one (Blockfrost / Ogmios / Demeter) or a UPLC evaluator. ### 🐛 Bug Fixes — `@hydra-sdk/transaction` - **Script-transaction fee fix**: the builder config previously hardcoded `ex_unit_prices` to zero and `ref_script_coins_per_byte` to `15/1`, so the script-execution fee component (`priceMem·exMem + priceStep·exSteps`) was omitted and reference-script pricing ignored the protocol params — under-funding script transactions. Both are now sourced from the protocol parameters (`priceMem`, `priceStep`, `minFeeRefScriptCostPerByte`). Non-script transfers were already correct. - **`reset()` use-after-free fixed** (the metadata container was freed but not recreated) and the constructor no longer double-allocates the underlying `TransactionBuilder` when `params` is supplied. ### 📦 `@hydra-sdk/cardano-wasm` v1.0.0 — first stable release - The WASM wrapper reaches **v1.0.0**. It selects the correct `@emurgo/cardano-serialization-lib` build via package.json `exports` conditions (`browser` → browser WASM, `node` → Node.js), with the asm.js fallback opt-in at the `@hydra-sdk/cardano-wasm/asmjs` subpath. Now that it is stable, pin it like any other dependency (`^1.0.0`). ### 🔧 `@hydra-sdk/core` v1.4.1 & `@hydra-sdk/bridge` v1.3.2 - **`@hydra-sdk/bridge@1.3.2`**: syncs the updated Cardano **v11** protocol parameters and cost models; depends on `core@1.4.1` and `cardano-wasm@1.0.0`. - **`@hydra-sdk/core@1.4.1`**: dependency bump to `@hydra-sdk/cardano-wasm@1.0.0` (patch; the v11 protocol-parameter defaults landed in `1.4.0`). ### 📦 Updated Packages - `@hydra-sdk/transaction@1.2.0` - `@hydra-sdk/core@1.4.1` - `@hydra-sdk/bridge@1.3.2` - `@hydra-sdk/cardano-wasm@1.0.0` --- ## Version 1.4.0 - July 2026 ### 🚀 New Features — `@hydra-sdk/core` - **`RedeemerUtils` — new namespace**: `RedeemerUtils.mkRedeemer(data, options?)` wraps any `PlutusData` (e.g. built with `DatumUtils`) into a `Redeemer` with configurable `tag` / `index` / `exUnits`, ready to attach to the `@hydra-sdk/transaction` `TxBuilder`. Convenience helpers `mkSpendRedeemer`, `mkMintRedeemer`, `mkUnitRedeemer`, plus low-level `mkRedeemerTag`, `mkExUnits` and `DEFAULT_EX_UNITS` are included. ```typescript import { RedeemerUtils, DatumUtils } from '@hydra-sdk/core' const data = DatumUtils.mkConstr(1, []) // e.g. a `Cancel` redeemer const redeemer = RedeemerUtils.mkRedeemer(data, { tag: 'spend', index: 0 }) ``` - **`DatumUtils` — new Plutus-data encoders**: - `mkBool` — encode an Aiken `Bool` (`False = Constr(0, [])`, `True = Constr(1, [])`). - `mkOption` — encode an `Option`/`Maybe` (`Some = Constr(0, [v])`, `None = Constr(1, [])`). - `mkBytesList` / `mkIntList` — encode `List` / `List`. - `mkOutputRef` — encode a Plutus `OutputReference` (`Constr(0, [Bytes(txHash), Int(index)])`). - `mkAddress` / `parseAddress` — convert a bech32 address to/from Plutus `Address` data. - **`Deserializer.deserializeAmountsFromTx(cborHex)`**: decodes a transaction CBOR and returns every amount (lovelace + native tokens) across **all** outputs, merged by `unit` with quantities summed. Returns `Asset[]`. ```typescript const amounts = Deserializer.deserializeAmountsFromTx(txCborHex) // [{ unit: 'lovelace', quantity: '2000000' }, { unit: '', quantity: '5' }] ``` ### ♻️ Refactor — `@hydra-sdk/core` - **Address validation moved out of `validator.util`**: `AddressUtils.isValidAddress` and the new `ValidationUtils.isValidTxOutput` now hold these helpers (previously in `validator.util`, a name easily confused with Plutus **validator scripts**). `ValidatorUtils` is retained as a **deprecated** re-export for backward compatibility — existing `ValidatorUtils.isValidAddress` / `ValidatorUtils.isValidTxOutput` calls keep working, but new code should use `AddressUtils.isValidAddress` / `ValidationUtils.isValidTxOutput`. ### 🔧 Protocol — `@hydra-sdk/core` - **Protocol parameters upgraded to v11**: `DEFAULT_PROTOCOL_PARAMETERS.minPoolCost` lowered from `340000000` (340 ADA) to `170000000` (170 ADA), matching the Cardano v11 protocol parameter update. - **Cost models synced with the v11 on-chain cost models**: `DEFAULT_V1_COST_MODEL_LIST` / `DEFAULT_V2_COST_MODEL_LIST` extended with the additional Plutus builtin cost entries; `DEFAULT_V3_COST_MODEL_LIST` coefficients updated to the v11 values. ### 🐛 Bug Fixes — `@hydra-sdk/core` - **`Resolver.resolveTxHash` WASM memory leak**: now calls `.free()` on the intermediate `FixedTransaction` WASM object before returning, preventing WASM heap growth in long-running processes that hash many transactions. ### 📦 Updated Packages - `@hydra-sdk/core@1.4.0` --- ## Version 1.3.2 - June 2026 ### 🚀 New Features — `@hydra-sdk/core` - **`DemeterProvider` — new provider**: Blockfrost-compatible provider for [Demeter](https://demeter.run){rel=""nofollow""} hosted endpoints. Extends `BlockfrostProvider` and builds the authenticated endpoint URL automatically from the `authToken` and `network` fields. ```typescript import { ProviderUtils } from '@hydra-sdk/core' const provider = new ProviderUtils.DemeterProvider({ authToken: 'blockfrost102lx3ckhzvkjjh7677g', network: 'mainnet', // 'mainnet' | 'preprod' | 'preview' }) ``` - **`BlockfrostProviderConfig.baseURL`**: new optional field allowing callers to override the default Blockfrost endpoint URL (also used internally by `DemeterProvider`). - **`DatumUtils.mkList(elements)`**: constructs a `PlutusData` list from an array of `PlutusData` elements, completing the datum builder API alongside `mkInt`, `mkBytes`, `mkConstr`, and `mkMap`. ### 🐛 Bug Fixes — `@hydra-sdk/core` - **`metadataObjToMetadatum` validation & logic fixes**: UTF-8 byte-length validation for strings and bytes against the 64-byte Cardano limit (with descriptive errors), a `Number.isInteger()` guard for float values, and a dedicated `Map` branch (previously an empty `Map` silently lost data). ### 📦 Updated Packages - `@hydra-sdk/core@1.3.2` --- ## Version 1.3.1 - March 18, 2026 ### 🐛 Bug Fixes — `@hydra-sdk/bridge` - **Greetings `Idle` head status crash**: When `headStatus` is `Idle` (head not yet opened), the `Greetings` message does not include a `snapshotUtxo` field. Accessing it caused an `undefined` error and broke the cache seeding path. The handler now guards against the missing field before calling `updateSnapshot`. ### 🛠️ Developer Experience — `@hydra-sdk/bridge` - **Chalk-coloured verbose logs**: All `console.log/warn/error` calls throughout `HydraBridge` have been replaced with three module-level helpers (`log`, `warn`, `err`) backed by `chalk`: - Tag `[⚡ HydraBridge]` is always printed in **cyan bold** - Success events (connected, snapshot seeded) use **green** - Warnings (reconnect attempts, out-of-order snapshots) use **yellow** - Errors and disconnection events use **red** - Low-signal values (txId, slot timestamp) use **gray** - **Debug dumps removed**: Two leftover `console.log('>>> / bridge.ts:...')` statements have been deleted. ### 📦 Updated Packages - `@hydra-sdk/bridge@1.3.1` --- ## Version 1.3.0 - March 16, 2026 ### 🚀 New Features — `@hydra-sdk/bridge` - **hydra-node v1.3.0 Compatibility**: `Greetings` message extended with four new optional fields: `currentSlot`, `chainSyncedStatus`, `env`, `networkInfo` - **Slot-Zero Timestamp**: `HydraBridge.slotZeroTimestamp` — automatically derived from `Greetings.currentSlot` for slot ↔ time arithmetic inside the Hydra Head - **`submitTx` callback API**: New Node.js error-first callback method alongside the existing `submitTxSync`: ```typescript bridge.submitTx(tx, (error, result) => { ... }, { timeout: 30000 }) ``` - **`/head` API fix**: `HydraHeadInfo` type rewritten to match the real hydra-node v1.3.0 `/head` response structure - **Auto-reconnect**: Three new `InitHydraBridgeOptions` fields — `autoReconnect`, `reconnectInterval` (default 3000ms), `maxReconnectAttempts` (0 = unlimited) ### ⚡ Performance — `@hydra-sdk/bridge` - **O(1) balance lookup**: Pre-computed `balanceCache: Map>` — rebuilt once per snapshot, read per request with no I/O - **O(1) UTxO lookup**: Pre-built `addressUtxoIndex: Map` — `queryAddressUTxO()` and `addressesInHead()` no longer scan all UTxOs on each call - **`getAddressBalance(address)`**: New public API for instant balance reads. Returns `null` on cold start (cache not yet seeded) - **`lastSnapshotNumber`**: Exposed as public field; cache only advances, never regresses on reconnect or out-of-order delivery - **Greetings seeds cache**: No HTTP round-trip needed on fresh connect — `snapshotUtxo` from the `Greetings` message seeds the index directly - **HTTP fallback race fix**: Slow HTTP response can no longer overwrite fresher WebSocket snapshot data ### ⚡ Performance — `@hydra-sdk/core` - **`convertUTxOObjectToUTxOWithOptions`**: New export accepting `{ maxDatumCacheSize?: number }` — datum deserialization cache is now bounded (default 1024) with LRU eviction, preventing unbounded memory growth on large snapshots - **Converter refactors**: `reduce` → `for` loops in `convertUTxOToUTxOObject` and `convertTxOutputToWasm`; `for...in` replaces `Object.entries()` in `convertUTxOObjectToUTxO` to avoid intermediate array allocations - **Benchmark script**: `scripts/bench-converter.ts` — CLI tool for measuring conversion throughput under configurable workloads ### 🐛 Bug Fixes — `@hydra-sdk/bridge` - **`submitTxSync` variable shadowing**: Inner `tx` shadowed outer `tx` parameter — always returned the first confirmed transaction instead of the matching one - **`newTx` hardcoded description**: Was overwriting caller-supplied description with `'Ledger Cddl Format'`; now passed through as-is - **URL builder default port**: `buildUrl` was emitting `https://host:443/path` when no explicit port was provided; default ports are now stripped ### 🛠️ Internal Refactoring - **`awaitHydraMessage` utility** (`src/utils/await-hydra-message.ts`): Replaces the repeated manual event-listener + timeout cleanup pattern. `submitTxSync`, `decommit`, and `initHydraHead` all refactored to use it — no more fragile multi-exit-path cleanup ### 📦 Updated Packages - `@hydra-sdk/core@1.3.0` - `@hydra-sdk/bridge@1.3.0` --- ## Version 1.1.7 - January 30, 2026 ### 🚀 New Features - **Reference Input Support**: Added support for Cardano reference inputs in `TxBuilder` via `txInReference()` method - **Reference Script Support**: Added support for attaching reference scripts to transaction outputs in `TxBuilder` via `txOutReferenceScript()` method - **CIP-8 Message Signing**: Implemented `signData()` method in `EmbeddedWallet` for arbitrary data signing (CIP-8) ### 🛠️ Technical Improvements - **Plutus Type Standardization**: Standardized Plutus-related types (`Datum`, `Redeemer`, `ScriptRef`) across core and transaction packages for better consistency - **Provider Enhancements**: Updated `OgmiosProvider` to support structured script reference retrieval ### 🐛 Bug Fixes - **HydraBridge**: Fixed `noHistory` option behavior in WebSocket connector ### 📦 Updated Packages - `@hydra-sdk/core@1.1.7` - `@hydra-sdk/transaction@1.1.7` - `@hydra-sdk/bridge@1.1.7` --- ## Version 1.1.6 - January 21, 2026 ### 🚀 New Features - **Hydra Hub Integration**: Enhanced `HydraBridge` with support for connecting to Hydra Hub - **Plutus Script Utilities**: Added comprehensive Plutus script utilities and enhanced `applyParamsToScript` functionality - **AI Assistance**: Enhanced AI assistance UI and functionality with streaming responses and improved markdown rendering ### 🛠️ Technical Improvements - **Web API Transition**: Replaced Node.js `Buffer` dependency with `TextEncoder`/`TextDecoder` for better browser compatibility - **Conversion Utilities**: Enhanced hex and byte conversion utilities with improved performance and error handling - **Key Management**: Improved mnemonic-to-key conversion functionality and key utilities ### 🧪 Testing & CI/CD - **Expanded Test Coverage**: Added comprehensive unit tests for `TxBuilder`, validator utilities, and metadata processing - **Modernized Testing Stack**: Updated Vitest to version 3.x and added shared TypeScript/ESLint configurations ### 📚 Documentation Updates - **Multi-language Support**: Added complete Japanese documentation and synchronized English/Vietnamese versions - **Provider Documentation**: Updated documentation for Blockfrost and Ogmios providers ### 📦 Updated Packages - `@hydra-sdk/core@1.1.6` - `@hydra-sdk/transaction@1.1.6` - `@hydra-sdk/bridge@1.1.6` --- ## Version 1.1.5 - November 27, 2025 ### 🛠️ Core Utilities - **Key Management Utilities**: Updated and expanded key management functions (keygen, verification key, Hydra key) in `@hydra-sdk/core` - Added unit tests for key generation functions - Standardized and optimized key management API for Hydra and Cardano CLI - **Blockfrost Integration**: Added configuration files and Blockfrost integration for demo applications ### 🧪 Testing & Metadata - **SDK API Metadata Extraction**: Added script to automatically generate SDK API metadata (`extract-sdk-api.ts`) - **Test Coverage**: Added unit tests for key utilities ### 📦 Updated Packages - `@hydra-sdk/core@1.1.5` - `@hydra-sdk/transaction@1.1.5` - `@hydra-sdk/bridge@1.1.5` --- ## Version 1.1.3 - October 29, 2025 ### 🧪 Testing & CI/CD Infrastructure - **GitHub Actions Workflows**: Comprehensive CI/CD pipeline cho automated testing - **Jest Configuration**: Thêm Jest config với coverage reporting và JUnit support - **Test Workflow**: Automated testing workflow với detailed reporting và coverage metrics - **GitHub Secrets Setup**: Documentation và configuration cho environment variables trong CI/CD - **Test Commands**: Thêm `test:ci` script cho continuous integration testing ### 🛠️ Core Utilities Improvements - **Metadata Utilities**: Thêm utility functions để convert các loại metadata khác nhau sang `CardanoWASM.TransactionMetadatum` - Support cho Map, List, String, Number, Boolean metadata types - Enhanced type safety cho Plutus datum operations - **Converter Fixes**: Fix và optimize converter utilities cho UTxO transformation - Improved performance cho conversion operations - Better error handling và validation ### 🏗️ Package Refactoring - **Package Rename**: Rename `hydra-wallet` thành `core` để đồng nhất với package naming convention - **Import Updates**: Refactor imports để sử dụng `Deserializer` và `Converter` từ centralized locations ### 🧹 Code Quality & Maintenance - **Debug Logs Cleanup**: Remove unnecessary debug logs trong WebsocketConnector - **Test Improvements**: Remove unnecessary logs và ensure conversion performance trong tests - **Dependencies Update**: Update pnpm lock với các dependency mới nhất ### 📚 Documentation Updates - **CI/CD Documentation**: Thêm comprehensive documentation cho GitHub Actions workflows - **Secrets Management**: Setup guide cho GitHub Secrets và environment variables - **Testing Guide**: Documentation cho Jest configuration và test workflows ### 📦 Updated Packages - `@hydra-sdk/core@1.1.3` - `@hydra-sdk/transaction@1.1.3` - `@hydra-sdk/bridge@1.1.3` ### 🔧 Developer Experience - **Automated Testing**: CI/CD pipeline tự động chạy tests trên mọi PR và push - **Coverage Reports**: Detailed coverage metrics và reporting trong GitHub Actions - **Better Tooling**: Improved development workflow với automated checks --- ## Version 1.1.2 - October 22, 2025 ### 🎉 New Features - **CardanoCliWallet Implementation**: New wallet class for Cardano CLI-based key management and transaction handling - **Key Management**: Support for signing keys, verification keys, and address generation - **Transaction Signing**: Direct transaction signing using Cardano CLI wallet format - **Node.js Integration**: Seamless integration with Node.js environments for CLI-based workflows ### 🛠️ Technical Improvements - **Wallet Abstraction**: Added CardanoCliWallet class to `@hydra-sdk/core` for CLI-based wallet operations - **Enhanced Flexibility**: Support for multiple wallet types (Browser extension, CLI, Hardware) - **Developer Experience**: Simplified wallet integration for Node.js applications and automation scripts ### 📦 Updated Packages - `@hydra-sdk/core@1.1.2` - `@hydra-sdk/transaction@1.1.2` - `@hydra-sdk/bridge@1.1.2` ### 📚 Package Updates - **Apps**: Updated dependencies across playground, nodejs-playground, and web applications - **Documentation**: Updated to reflect new CardanoCliWallet functionality --- ## Version 1.1.1 - October 8, 2025 ### 🎉 New Features - **Hydra Concept Documentation**: Comprehensive new documentation section covering Hydra Layer 2 concepts and usage - **Why Hydra?**: Complete guide explaining benefits, use cases, and when to use Hydra for your project - **Commit to Hydra**: Detailed documentation on committing UTxOs into Hydra Heads with blueprint transaction support - **Blueprint Transaction Support**: Enhanced TxBuilder with blueprint transaction functionality for Hydra deposits - **Incremental Commit**: Added support for partial deposits with blueprint transactions and change address handling ### 🛠️ Technical Improvements - **TxBuilder Enhancement**: Added blueprint transaction support for building transactions compatible with Hydra commit operations - **HydraApi Extensions**: New `commit()` and `partialDeposit()` methods in nodejs-playground for Hydra operations - **Namespace Imports**: Migrated from direct function imports to namespace exports (`Deserializer`, `ParserUtils`) - **Package Export Optimization**: Updated package exports to use source files for better development experience ### 📚 Documentation Updates - **Hydra Concept Section**: Complete bilingual documentation (English/Vietnamese) for Hydra Layer 2 concepts - **Interactive Examples**: Added practical Node.js examples for empty commits and blueprint-based commits - **Navigation Enhancement**: New "Hydra Concept" section in documentation sidebar with comprehensive guides - **Code Examples**: Real-world examples for partial commits, blueprint transactions, and change address handling ### 📦 Updated Packages - `@hydra-sdk/core@1.1.1` - `@hydra-sdk/transaction@1.1.1` - `@hydra-sdk/bridge@1.1.1` ### ⚡ Performance & Development Experience - **Source-based Exports**: Package exports now point to source files instead of built files for faster development - **Enhanced Type Safety**: Improved TypeScript definitions for Hydra operations - **Better Error Handling**: Enhanced error logging and debugging for Hydra transactions --- ## Version 1.1.0 - September 26, 2025 ### 🎉 New Features - **Utilities Modernization**: Major update to core utilities with enhanced APIs and modern patterns - **TimeUtils**: New `unixTimeToEnclosingSlot()` and `slotToBeginUnixTime()` functions replacing deprecated `resolveSlotNo()` - **SLOT\_CONFIG\_NETWORK**: Network-specific slot configuration constants (MAINNET, PREPROD, PREVIEW) - **Serializer/Deserializer**: Namespace exports for `Serializer.serializeAssetUnit()` and `Deserializer.deserializeAssetUnit()` - **DatumSchema**: Simplified to constants-only API (Basic, Detailed) - removed deprecated methods ### 📚 Documentation Overhaul (September 26, 2025) - **Comprehensive Documentation Modernization**: Complete rewrite of utilities examples and guides (50-75% size reduction) - **TimeUtils API Updates**: Updated all examples to use new `unixTimeToEnclosingSlot()` patterns with `SLOT_CONFIG_NETWORK` - **API Accuracy Fixes**: Corrected DatumSchema documentation to reflect actual implementation (constants only) - **Transaction Signing Streamline**: Reduced transaction signing examples from \~400 to \~150 lines, focusing on 4 core patterns - **Working with Utilities Modernization**: Updated guides with actual patterns from nodejs-playground - **Serializer/Deserializer Patterns**: Modernized all examples to use namespace exports and round-trip workflows - **Perfect EN/VI Sync**: Maintained 100% consistency between English and Vietnamese documentation ### 🛠️ Technical Improvements - **Breaking API Changes**: Deprecated legacy utility functions in favor of modern patterns - **Namespace Exports**: Migrated from direct function imports to namespace pattern for better organization - **Production Patterns**: All examples now based on real implementation patterns from nodejs-playground - **Enhanced Error Handling**: Added comprehensive error handling patterns for utilities - **Performance Optimizations**: Streamlined utility functions for better performance ### 📦 Updated Packages - `@hydra-sdk/core@1.1.0` - `@hydra-sdk/transaction@1.1.0` - `@hydra-sdk/bridge@1.1.0` ### ⚠️ Breaking Changes - **TimeUtils**: `resolveSlotNo()` deprecated - use `unixTimeToEnclosingSlot()` with `SLOT_CONFIG_NETWORK` - **DatumSchema**: `fromJSON()`/`toJSON()` methods removed - use `DatumSchema.Basic`/`DatumSchema.Detailed` constants - **Serializer Import**: Direct `serializeAssetUnit` import deprecated - use `Serializer.serializeAssetUnit` --- ## Version 1.0.14 - September 24, 2025 ### 🎉 New Features - **Token Minting & Burning**: Implemented comprehensive minting and burning functionality with policy scripts and datum utilities - **Plutus Schema Support**: Added detailed schema support for Plutus datums in DatumSchema with enhanced type safety - **Policy Management**: Added PolicyUtils for creating and managing minting policies with time-based constraints - **Native Token Operations**: Full support for Cardano native token creation, metadata handling, and lifecycle management ### 📚 Documentation Updates - **Mint/Burn Guide**: Added comprehensive bilingual guide for token minting and burning operations - **Code Examples**: Included practical TypeScript examples for mint-burn-token operations - **Tutorial Content**: Created step-by-step tutorials covering wallet setup, UTxO management, and transaction building - **Security Guidelines**: Added security best practices and warnings for token operations ### 🛠️ Technical Improvements - **DatumUtils Enhancement**: Expanded datum utilities with support for complex Plutus data structures - **Transaction Builder**: Enhanced TxBuilder with native asset support and policy script integration - **WASM Integration**: Improved Cardano WASM deserializer for handling complex transaction types - **Type Safety**: Added comprehensive TypeScript types for Plutus scripts and native assets ### 📦 Updated Packages - `@hydra-sdk/core@1.0.14` - `@hydra-sdk/transaction@1.0.14` - `@hydra-sdk/bridge@1.0.14` --- ## Version 1.0.13 - September 22, 2025 ### � New Features - **GitHub Actions Integration**: Added comprehensive GitHub Actions workflow for automated building and testing - **Enhanced Contract Functionality**: Implemented era summary retrieval and time-based constraints in unlock processes - **Network Utilities**: Refactored network constants and added slot configuration utilities for improved time handling ### 🐛 Bug Fixes - **Validity Range**: Fixed validity range setup in transaction to correctly handle lower/upper bounds and serialization - **Time Utilities**: Added TimeUtils to core for common time conversions and helpers ### 📚 Documentation Updates - September 23, 2025 - **Vietnamese Documentation**: Added complete Vietnamese translation for all documentation pages - **i18n Support**: Implemented internationalization with language switcher (English/Vietnamese) - **Navigation Updates**: Added 'Hydra Bridge' entry to navigation and glossary localization - **Change Logs Integration**: Added 'Change Logs' link to documentation sidebar for better navigation - **Configuration Guide**: Updated configuration examples to optimize polyfills (Buffer only, disable global/process) - **Documentation Links**: Updated logo image paths and documentation links to reflect new SDK URLs - **License Update**: Updated license from MIT to Apache 2.0 across documentation - **Support Links**: Updated support banner link to point to the new funding page ### � Updated Packages - `@hydra-sdk/core@1.0.13` - `@hydra-sdk/transaction@1.0.13` - `@hydra-sdk/bridge@1.0.13` --- ## Version 1.0.12 - September 18, 2025 ### 🎉 New Features - **GitHub Actions Integration**: Added comprehensive GitHub Actions workflow for automated building and testing - **Enhanced Contract Functionality**: Implemented era summary retrieval and time-based constraints in unlock processes - **Network Utilities**: Refactored network constants and added slot configuration utilities for improved time handling - **Documentation Updates**: Updated logo image paths and documentation links to reflect new SDK URLs ### 🐛 Bug Fixes - **Validity Range**: Fixed validity range setup in transaction to correctly handle lower/upper bounds and serialization - **Time Utilities**: Added TimeUtils to core for common time conversions and helpers ### 📦 Updated Packages - `@hydra-sdk/core@1.0.13` - `@hydra-sdk/transaction@1.0.13` - `@hydra-sdk/bridge@1.0.13` --- ## Version 1.0.12 - September 18, 2025 ### 🚀 Major Improvements - **Transaction Builder**: Complete implementation of transaction building and unlocking functionality with Plutus scripts - **Custom Signing**: Implemented custom transaction signing functionality - **Script Enhancement**: Enhanced TxBuilder with script data hash recalculation and updated redeemer builder ### ⚡ Performance Enhancements - **Redeemer Handling**: Refactored redeemer handling in TxBuilder to use emptyRedeemer utility for improved clarity - **Execution Units**: Added exUnits parameter to buildRedeemer and emptyRedeemer functions for customizable execution units ### 🐛 Bug Fixes - **Transaction Builder Fixes**: - Updated descriptions for better clarity - Fixed redeemer builder with more options and changed default exUnits - Removed debug logs for cleaner output - Fixed calculate script hash functionality ### 📦 Updated Packages - `@hydra-sdk/core@1.0.12` - `@hydra-sdk/transaction@1.0.12` - `@hydra-sdk/bridge@1.0.12` --- ## Version 1.0.11 - September 15, 2025 ### 📚 Documentation & UI - **Version Badges**: Added version badges to package navigation in documentation - **Code Examples**: Updated code blocks in Vue.js example documentation for proper syntax highlighting - **Full App Examples**: Added comprehensive React and Vue.js application examples - **Links Enhancement**: Added links for Full React and Full Vue.js App examples ### 🧹 Maintenance - **Dependencies**: Removed chalk dependency from pnpm-lock.yaml - **Package Updates**: Updated version numbers and changelogs for multiple packages ### 📦 Updated Packages - `@hydra-sdk/core@1.0.11` - `@hydra-sdk/transaction@1.0.11` - `@hydra-sdk/bridge@1.0.11` --- ## Version 1.0.10 & Earlier Versions ### 🔧 Core Development Phase - **Unit Testing**: Added comprehensive unit tests and demo code - **API Implementation**: Implemented HexcoreApi and OgmiosApi classes - **Lock/Unlock Functionality**: Added lock and unlock transaction functionality - **Connection Management**: Updated HexcoreConnector to use new API endpoints - **Repository Migration**: Updated all GitHub links to point to Vtechcom organization - **License**: Added MIT License file - **Documentation**: Created initial documentation for Hydra SDK packages ### 🏗️ Infrastructure Setup - **Build Scripts**: Added build script for generating documentation - **Metadata Handling**: Updated transaction metadata functionality - **Node.js Support**: Implemented Node.js compatibility - **WebSocket Connector**: Fixed auto connect with websocket connector - **Auth System**: Enhanced auth page with version display and safety notice ### 📦 Package History - Multiple iterations of core packages (`@hydra-sdk/core`, `@hydra-sdk/transaction`, `@hydra-sdk/bridge`) - Legacy packages migration from `@hydrawallet-sdk/*` to `@hydra-sdk/*` - Configuration packages (`@hydra-sdk/tsconfig`, `@hydra-sdk/eslint-config`) - Cardano WASM integration (`@hydra-sdk/cardano-wasm`) --- ## 🔄 Migration Notes ### Package Naming Convention Starting from version 1.0.5, all packages have been migrated from `@hydrawallet-sdk/*` to `@hydra-sdk/*` naming convention for better consistency. ### Recommended Configuration (v1.0.13+) For optimal performance, we recommend updating your Vite configuration: **Recommended Setup:** ```javascript nodePolyfills({ include: ['buffer'], // ✅ Only include buffer polyfill globals: { Buffer: true, // ✅ Required for cryptographic operations global: false, // ✅ Disabled for better performance process: false // ✅ Disabled for better performance } }) ``` **Legacy Setup (still supported):** ```javascript nodePolyfills({ globals: { Buffer: true, global: true, // ⚠️ Not recommended but still works process: true // ⚠️ Not recommended but still works } }) ``` ### Breaking Changes - **v1.1.0**: Major TimeUtils API changes - `resolveSlotNo()` deprecated, use `unixTimeToEnclosingSlot()` with `SLOT_CONFIG_NETWORK` - **v1.1.0**: DatumSchema API simplified - removed `fromJSON()`/`toJSON()` methods, use constants only - **v1.1.0**: Serializer imports - use namespace exports `Serializer.serializeAssetUnit()` instead of direct imports - **v1.0.14**: Added mint/burn token functionality with new DatumUtils and PolicyUtils structures - **v1.0.13**: License change from MIT to Apache 2.0 (documentation only) - **v1.0.12**: Updated redeemer builder interface with new exUnits parameter - **v1.0.11**: Changed transaction validity range handling - **v1.0.10**: Repository URLs updated to Vtechcom organization --- ## 📋 Package Overview | Package | Current Version | Description | | -------------------------- | --------------- | ---------------------------------------------------- | | `@hydra-sdk/core` | 1.4.1 | Core library for building decentralized applications | | `@hydra-sdk/transaction` | 1.2.0 | Transaction building and management utilities | | `@hydra-sdk/bridge` | 1.3.2 | Bridge functionality for wallet connections | | `@hydra-sdk/cardano-wasm` | 1.0.0 | Cardano WASM bindings and utilities | | `@hydra-sdk/tsconfig` | 0.0.4 | Shared TypeScript configuration | | `@hydra-sdk/eslint-config` | 0.0.4 | Shared ESLint configuration | --- ## 🔗 Links & Resources - **GitHub Repository**: {rel=""nofollow""} - **Documentation**: {rel=""nofollow""} - **npm Registry**: [@hydra-sdk packages](https://www.npmjs.com/search?q=%40hydra-sdk){rel=""nofollow""} · [@hydra-sdk/core](https://www.npmjs.com/package/@hydra-sdk/core){rel=""nofollow""} - **Discord**: [Join the community](https://discord.com/invite/eZKRyQnbea){rel=""nofollow""} - **Telegram**: [Join the group](https://telegram.me/+LeuWUO7YjGYyZmJl){rel=""nofollow""} - **X**: [@VtechcomLabs](https://x.com/VtechcomLabs){rel=""nofollow""} --- *Change logs are automatically generated based on Git history and semantic versioning. For detailed commit information, please refer to the [GitHub repository](https://github.com/Vtechcom/hydra-sdk){rel=""nofollow""}.* # Migration ## `@hydra-sdk/bridge` v1.x → v2.0 `@hydra-sdk/bridge` **2.0** targets `hydra-node` **2.x**. It is a breaking change, and bridge **1.x is end of life**. ::warning v2 is published under the `next` dist-tag — `latest` still resolves to 1.3.2. :: ```bash pnpm add @hydra-sdk/bridge@next ``` ### Why it breaks hydra-node v2 implements ADR-33 and **removes the commit phase**. A head now opens directly, so there is nothing to abort and no `Committed` step; funds enter an open head through **incremental deposits** instead. ### What you have to change | v1.x | v2.0 | | ----------------------------- | ----------------------------------------------------------------------------------------------------- | | `bridge.commands.abort()` | Removed — no commit phase to abort | | `bridge.commands.initSync()` | Resolves on `HeadIsOpen`, not `HeadIsInitializing` | | `HeadIsOpen` handlers | Reads `parties`; `utxo` is gone — take the UTxO set from `SnapshotConfirmed` or `querySnapshotUtxo()` | | `HeadIsFinalized.utxo` | Renamed to `finalizedUTxO` | | `HydraHeadStatus` | No `Initializing` / `Final` | | `HydraHeadInfo` | Now a union over \`Idle | | `Greetings` / `ClientMessage` | No `seq` / `timestamp` — they are sent untimed | ### Full details The complete list, including the new APIs (`safeClose()`, `submitL2Tx()`, `pendingDeposits()`, `recoverDeposit()`) and the wire-format fixes found by testing against a live node, is in [MIGRATION-v2.md](https://github.com/Vtechcom/hydra-sdk/blob/master/packages/hydra-bridge/MIGRATION-v2.md){rel=""nofollow""}. --- ## Migration to v1.1.x (legacy) This guide helps you upgrade from previous versions of Hydra SDK to v1.1.x, which introduces a new utilities system and improved developer experience. ## What's Changed ### New Utilities System Version 1.1.0 introduces a completely revamped utilities system with organized namespaces: **Before (v1.0.x):** ```typescript // Individual utility imports (if available) import { someUtility } from '@hydra-sdk/core/utils/some-utility' ``` **After (v1.1.x):** ```typescript // Organized namespace imports import { ParserUtils, TimeUtils, DatumUtils, PolicyUtils, Serializer, Deserializer } from '@hydra-sdk/core' ``` ## Breaking Changes ### 1. Utility Function Organization **Before:** ```typescript // Direct imports from utils (if they existed) import { hexToBytes, bytesToHex } from '@hydra-sdk/core/utils' ``` **After:** ```typescript // Namespaced utility imports import { ParserUtils } from '@hydra-sdk/core' const hex = ParserUtils.bytesToHex(bytes) const bytes = ParserUtils.hexToBytes(hex) ``` ### 2. Time/Slot Utilities **Before:** ```typescript // Manual slot calculations or external libraries const currentSlot = calculateSlot(Date.now()) ``` **After:** ```typescript import { TimeUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core' const currentSlot = TimeUtils.unixTimeToEnclosingSlot( Date.now(), SLOT_CONFIG_NETWORK.PREPROD ) const futureSlot = TimeUtils.unixTimeToEnclosingSlot( Date.now() + 3600000, SLOT_CONFIG_NETWORK.PREPROD ) ``` ### 3. Datum Creation **Before:** ```typescript // Manual WASM operations import { CardanoWASM } from '@hydra-sdk/cardano-wasm' const datum = CardanoWASM.PlutusData.new_integer( CardanoWASM.BigInt.from_str('42') ) ``` **After:** ```typescript import { DatumUtils } from '@hydra-sdk/core' const datum = DatumUtils.mkInt(42) const bytesDatum = DatumUtils.mkBytes('deadbeef') const constrDatum = DatumUtils.mkConstr(0, [datum, bytesDatum]) ``` ## Migration Steps ### Step 1: Update Dependencies ```bash # Update to latest version npm update @hydra-sdk/core @hydra-sdk/bridge @hydra-sdk/transaction @hydra-sdk/cardano-wasm # Or with specific version npm install @hydra-sdk/core@^1.1.0 @hydra-sdk/cardano-wasm ``` ### Step 2: Update Imports Replace individual utility imports with namespace imports: ```typescript // OLD - Remove these import { someUtility } from '@hydra-sdk/core/utils/some-utility' import { CardanoWASM } from '@hydra-sdk/cardano-wasm' // NEW - Use these instead import { ParserUtils, TimeUtils, DatumUtils, PolicyUtils, Serializer, Deserializer, Resolver, Converter, BuildKeys, ProviderUtils, CostModels } from '@hydra-sdk/core' ``` ### Step 3: Replace Manual Operations #### Data Conversion ```typescript // OLD const hex = Buffer.from(bytes).toString('hex') const bytes = Buffer.from(hex, 'hex') // NEW import { ParserUtils } from '@hydra-sdk/core' const hex = ParserUtils.bytesToHex(bytes) const bytes = ParserUtils.hexToBytes(hex) ``` #### Time Calculations ```typescript // OLD const slot = Math.floor((Date.now() - SHELLEY_START) / SLOT_LENGTH) + SHELLEY_SLOT_START // NEW import { TimeUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core' const slot = TimeUtils.unixTimeToEnclosingSlot(Date.now(), SLOT_CONFIG_NETWORK.PREPROD) ``` #### Datum Creation ```typescript // OLD - Complex WASM operations import { CardanoWASM } from '@hydra-sdk/cardano-wasm' const datum = CardanoWASM.PlutusData.new_constr_plutus_data( CardanoWASM.ConstrPlutusData.new( CardanoWASM.BigNum.from_str('0'), list ) ) // NEW - Simple utility functions import { DatumUtils } from '@hydra-sdk/core' const datum = DatumUtils.mkConstr(0, [field1, field2]) ``` ### Step 4: Update Provider Usage If you were using custom provider implementations: ```typescript // OLD - Custom provider setup class MyProvider { async getUtxos(address) { // Custom implementation } } // NEW - Use built-in providers or extend base import { ProviderUtils } from '@hydra-sdk/core' const provider = new ProviderUtils.BlockfrostProvider({ apiKey: 'your-blockfrost-api-key', network: 'preprod' }) // Or extend a built-in provider to customize behavior class MyProvider extends ProviderUtils.BlockfrostProvider { constructor() { super({ apiKey: 'your-blockfrost-api-key', network: 'preprod' }) } // Inherited `.fetcher` / `.submitter` still work async getUtxos(address: string) { return this.fetcher.fetchAddressUTxOs(address) } } ``` ## Common Migration Patterns ### Pattern 1: Metadata Creation **Before:** ```typescript const metadata = { 721: { [policyId]: { [tokenName]: { name: Buffer.from(name).toString('hex'), image: Buffer.from(image).toString('hex') } } } } ``` **After:** ```typescript import { ParserUtils, DatumUtils } from '@hydra-sdk/core' const metadata = { 721: { [policyId]: { [tokenName]: { name: ParserUtils.stringToHex(name), image: ParserUtils.stringToHex(image) } } } } // Or create as datum const metadataDatum = DatumUtils.mkMap([ [DatumUtils.mkBytes(ParserUtils.stringToHex("name")), DatumUtils.mkBytes(ParserUtils.stringToHex(name))], [DatumUtils.mkBytes(ParserUtils.stringToHex("image")), DatumUtils.mkBytes(ParserUtils.stringToHex(image))] ]) ``` ### Pattern 2: Transaction Timing **Before:** ```typescript // Manual slot calculation const validFrom = getCurrentSlot() const validUntil = validFrom + 7200 // 1 hour in slots ``` **After:** ```typescript import { TimeUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core' const validFrom = TimeUtils.unixTimeToEnclosingSlot(Date.now(), SLOT_CONFIG_NETWORK.PREPROD) const validUntil = TimeUtils.unixTimeToEnclosingSlot(Date.now() + 3600000, SLOT_CONFIG_NETWORK.PREPROD) ``` ### Pattern 3: Policy Script Creation **Before:** ```typescript // Manual WASM operations for policy import { CardanoWASM } from '@hydra-sdk/cardano-wasm' const keyHash = CardanoWASM.Ed25519KeyHash.from_hex(publicKeyHash) const scriptPubkey = CardanoWASM.ScriptPubkey.new(keyHash) const policy = CardanoWASM.NativeScript.new_script_pubkey(scriptPubkey) ``` **After:** ```typescript import { PolicyUtils } from '@hydra-sdk/core' const policy = PolicyUtils.buildPolicyScriptFromPubkey({ type: 'sig', keyHash: publicKeyHash }) // Or from address const policy = PolicyUtils.buildMintingPolicyScriptFromAddress(address) ``` ## Testing Your Migration ### 1. Basic Functionality Test ```typescript import { AppWallet, NETWORK_ID, ParserUtils, TimeUtils, DatumUtils } from '@hydra-sdk/core' // Test wallet creation (should work as before) const wallet = new AppWallet({ networkId: NETWORK_ID.PREPROD, key: { type: 'mnemonic', words: AppWallet.brew() } }) // Test new utilities const hex = ParserUtils.stringToHex('test') const slot = TimeUtils.resolveSlotNo('preprod') const datum = DatumUtils.mkInt(42) console.log('Migration test passed:', { address: wallet.getAccount(0, 0).baseAddressBech32, hex, slot, datumHex: datum.to_hex() }) ``` ### 2. Data Conversion Test ```typescript import { ParserUtils } from '@hydra-sdk/core' const testData = [ 'Hello World', '🚀 Hydra SDK', 'Special chars: åøæ' ] testData.forEach(str => { const hex = ParserUtils.stringToHex(str) const back = Buffer.from(hex, 'hex').toString('utf8') console.assert(str === back, `Conversion failed for: ${str}`) }) console.log('Data conversion tests passed') ``` ### 3. Time Utilities Test ```typescript import { TimeUtils, SLOT_CONFIG_NETWORK } from '@hydra-sdk/core' const networks = ['MAINNET', 'PREPROD', 'PREVIEW'] as const networks.forEach(network => { const slot = TimeUtils.unixTimeToEnclosingSlot(Date.now(), SLOT_CONFIG_NETWORK[network]) console.assert(typeof slot === 'number' && slot > 0, `Invalid slot for network: ${network}`) }) console.log('Time utilities tests passed') ``` ## Troubleshooting ### Common Issues 1. **Import Errors** ```text Error: Module not found: @hydra-sdk/core/utils/... ``` :br**Solution**: Use namespace imports instead of deep imports. 2. **Type Errors with Utilities** ```text Error: Property 'stringToHex' does not exist on type... ``` :br**Solution**: Import the correct namespace: `ParserUtils.stringToHex` 3. **Slot Calculation Issues** ```text Error: Invalid network parameter ``` :br**Solution**: Use exact network strings: `'mainnet'`, `'preprod'`, or `'preview'` ### Getting Help If you encounter issues during migration: 1. Check the [Utilities API Reference](https://hydrasdk.com/api/utilities) 2. Review [Working with Utilities Guide](https://hydrasdk.com/guides/utilities-cookbook) 3. Look at [Utilities Examples](https://hydrasdk.com/guides/utilities-cookbook) 4. Join our community Discord for support ## Benefits of v1.1.0 After migration, you'll enjoy: - **Better Developer Experience**: Organized, predictable utility functions - **Type Safety**: Comprehensive TypeScript definitions - **Performance**: Optimized utility implementations - **Maintainability**: Cleaner, more readable code - **Future-Proof**: Foundation for upcoming features The migration effort pays off with significantly improved development experience and code quality. # Performance & Comparison ## Why WASM for Hydra SDK? Hydra SDK leverages **WebAssembly (WASM)** to overcome the limitations of traditional JavaScript-based SDKs like `@cardano-sdk`. Here’s why WASM is the optimal choice: ### Key Advantages of WASM 1. **Eliminates Node.js Core Module Dependencies**: - WASM allows critical functionalities (e.g., cryptography, CBOR parsing, transaction building) to run in a sandboxed environment without relying on Node.js core modules like `crypto`, `stream`, or `fs`. - This ensures compatibility across both browser and Node.js environments. 2. **Reduces Bundle Size**: - WASM loads only the required functions, avoiding the need for polyfills like `crypto-browserify` or `stream-browserify`. - This results in significantly smaller bundles compared to SDKs that rely on Node.js modules. 3. **Boosts Performance**: - WASM executes cryptographic operations and transaction parsing at near-native speeds, reducing the overhead of JavaScript runtime. 4. **Cross-Platform Compatibility**: - A single WASM binary can run seamlessly on browsers, Node.js (via WASI), and mobile platforms (via bridges). - This eliminates the need for complex interop between CommonJS and ESM modules. --- ## Comparison of Cardano SDK Solutions ### Detailed Comparison Table | Criteria | **SDKs based on @cardano-sdk** | **Hydra SDK** (WASM-based) | | ----------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | **Browser Compatibility** | Requires polyfills for Node.js core modules; some APIs (e.g., `fs`, `net`) are unavailable. | Native browser support; no polyfills needed. | | **Bundle Size** | Very large (hundreds of KB to >1MB) due to polyfills. | Compact (only loads WASM + minimal JS glue code). | | **Node.js Dependency** | High, with direct imports of Node.js core modules. | None; all core logic resides in WASM. | | **Interop (ESM/CJS)** | Prone to errors (`exports is not defined`, `.default is not a function`). | No issues; pure ESM JavaScript. | | **Crypto/CBOR Performance** | Moderate (pure JS, dependent on low-performance polyfills). | High (WASM near-native speed). | | **Cross-Platform Support** | Good for Node.js, limited for browsers. | Excellent; works on browsers, Node.js, and mobile. | | **Tree-Shaking** | Limited, as Node.js imports are scattered, leading to unused code in bundles. | Excellent; only required WASM functions are bundled. | | **Maintenance & Scalability** | Complex, as it requires tracking Node.js API changes and polyfills. | Easy to maintain; core logic is implemented in Rust/C++ and exposed via WASM. | --- ## Dependency Flow Diagrams ### Issues with SDKs based on `@cardano-sdk` ```mermaid graph TD A[App Browser] --> B[SDKs based on @cardano-sdk] B --> C[Node Core Modules] C -->|Requires Polyfill| D[crypto-browserify, stream-browserify, buffer, util...] D -->|Increases Bundle Size| E[Runtime Errors without Polyfill] C -->|Cannot Polyfill| F[fs, net -> Unusable Features] ``` ### Hydra SDK (WASM-based) ```mermaid graph TD A[App Browser] --> B[Hydra SDK] B --> C["WASM Core (Rust/C++)"] C --> D[WebAssembly Runtime] D -->|Native Browser API| E[Crypto, CBOR, Tx Builder] D -->|Node.js WASI| F[Crypto, CBOR, Tx Builder] ``` --- ## Conclusion Hydra SDK’s WASM-based architecture provides a robust, efficient, and cross-platform solution for modern Cardano DApps. By eliminating Node.js dependencies, reducing bundle size, and boosting performance, it addresses the limitations of SDKs based on `@cardano-sdk`. --- ## In-Memory Snapshot Cache (v1.3.0+) `@hydra-sdk/bridge` adds a two-level in-memory cache that is rebuilt once per snapshot event and read O(1) per request. ### Architecture ```text Hydra Node (WebSocket) │ ├── Greetings { snapshotUtxo } │ └── updateSnapshot() ← O(n) one-time rebuild │ └── SnapshotConfirmed { snapshot.utxo } └── updateSnapshot() ← O(n) one-time rebuild, only if snapNum advances GET balance / query UTxO └── Map.get(address) ← O(1), no I/O, no WASM call ``` ### Performance Impact | Operation | Before v1.3.0 | v1.3.0+ | | ---------------------------- | -------------------------- | -------------------------------- | | `getAddressBalance()` | Not available | O(1) map lookup, no I/O | | `queryAddressUTxO(address)` | O(n) filter over all UTxOs | O(1) index lookup | | `addressesInHead()` | O(n) + HTTP call | O(1) `Map.keys()` | | 100 concurrent balance reads | 100 × scan + alloc | 100 × O(1) | | Snapshot arrival | O(1) (store ref) | O(n) rebuild — once per snapshot | ### Memory Usage For a 5 000-UTxO snapshot with 2 000 unique addresses averaging 2 assets each: ```text addressUtxoIndex: ~200 KB (2000 Map entries) balanceCache: ~256 KB (4000 inner Map entries) ──────────────────────────────────────────────── Total overhead: ~500 KB ``` ### Usage ```typescript import { HydraBridge } from '@hydra-sdk/bridge' const bridge = new HydraBridge({ url: 'ws://localhost:4001' }) await bridge.connect() // After Greetings arrives, cache is seeded automatically const balance = bridge.getAddressBalance('addr_test1...') if (balance === null) { // Cold start — cache not yet seeded } else { const lovelace = balance.get('lovelace') ?? 0n console.log('Balance:', Number(lovelace) / 1_000_000, 'ADA') } ``` ### Bounded Datum Cache (`@hydra-sdk/core`) `convertUTxOObjectToUTxOWithOptions` accepts `maxDatumCacheSize` to cap the number of deserialized inline datums kept in memory: ```typescript import { Converter } from '@hydra-sdk/core' const utxos = Converter.convertUTxOObjectToUTxOWithOptions(snapshotUtxo, { maxDatumCacheSize: 1024 // default; set to 0 to disable }) ``` Entries are evicted in insertion order (oldest first) when the limit is reached. --- ## TxBuilder WASM Memory (v1.2.0+) Every `@emurgo/cardano-serialization-lib` object lives in **WASM linear memory** and is only reclaimed when you call `.free()`. CSL relies on a `FinalizationRegistry` for automatic cleanup, but that runs non-deterministically and lags far behind a tight build loop. Before v1.2.0, building thousands of transactions in a row (e.g. spike-building inside a Hydra Head) leaked the WASM heap and made each successive `build_tx()` slower. ### What changed `@hydra-sdk/transaction@1.2.0` frees the WASM objects it allocates **deterministically**: - Every internally-allocated CSL object during `complete()` is tracked and freed in a `finally` block after `build_tx()`. The returned `Transaction` is an independent struct and is unaffected; caller-supplied objects (datum / redeemer / script) are never freed. - `reset()`, `changeAddress()`, and `updateProtocolParams()` free the state objects they replace. - `dispose()` (and `[Symbol.dispose]`) release everything the builder still holds. ### Measured impact | Metric | Before v1.2.0 | v1.2.0+ | | ---------------------------- | ----------------- | ------------------- | | WASM heap over 10,000 builds | Grows unbounded | Flat (\~0 KB/iter) | | Per-transaction build time | Degrades steadily | Steady | | Existing test suite | — | 280 tests unchanged | ### Patterns for high-volume workloads Pick the path that frees memory for you: ```typescript import { TxBuilder } from '@hydra-sdk/transaction' // 1. completeCbor() — you only need the bytes; the Transaction is freed for you const cbor = await new TxBuilder() .setInputs(utxos) .txOut(addr, [{ unit: 'lovelace', quantity: '1000000' }]) .changeAddress(addr) .completeCbor() // 2. `using` — builder disposed automatically at end of scope (TS 5.2+) { using builder = new TxBuilder() const bytes = await builder.setInputs(utxos)./* … */.completeCbor() } // 3. Reuse one builder across a spike, then dispose once const builder = new TxBuilder() for (const job of jobs) { const bytes = await builder.reset().setInputs(job.utxos)./* … */.completeCbor() } builder.dispose() ``` ::warning If you keep the live `Transaction` from `complete()` (e.g. `complete()` + `.to_hex()`), **you** own it — call `tx.free()` when done. Under high volume, prefer `completeCbor()`. :: # Use with AI These docs are built to be consumed by AI assistants and LLM tools. Three access methods ship out of the box — an MCP server, `llms.txt` files, and per-page raw markdown. ## MCP server A [Model Context Protocol](https://modelcontextprotocol.io){rel=""nofollow""} server is exposed at `/mcp` (`https://hydrasdk.com/mcp`) over streamable HTTP, letting an AI assistant query the documentation directly instead of guessing. The server is named **Hydra SDK** and provides two tools: | Tool | Purpose | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------- | | `list-pages` | List every documentation page with its title, path, and description — use it to explore or search when you don't know the exact page. | | `get-page` | Fetch the full markdown of a specific page by path (e.g. `/getting-started/installation`). | A typical flow: call `list-pages` to discover the right path, then `get-page` to read it. ### Connect ::code-group ```bash [Claude Code] claude mcp add --transport http hydra-sdk https://hydrasdk.com/mcp ``` ```json [mcp.json] { "mcpServers": { "hydra-sdk": { "url": "https://hydrasdk.com/mcp" } } } ``` :: ::tip When you run the docs locally (`pnpm dev`, port 3003), the MCP server is at `http://localhost:3003/mcp`, and the dev server prints an **Install Nuxt MCP server** deep link on startup — open it to register the server in your editor automatically. :: ## llms.txt Following the [llms.txt](https://llmstxt.org){rel=""nofollow""} convention, two files give an LLM the whole documentation as context: - **[/llms.txt](https://hydrasdk.com/llms.txt){rel=""nofollow""}** — an index of every page with links and descriptions. - **[/llms-full.txt](https://hydrasdk.com/llms-full.txt){rel=""nofollow""}** — the entire documentation concatenated into a single file. Point an LLM-aware tool at either URL, or paste the contents into a chat to give the model full context about Hydra SDK. ## Raw markdown Add `.md` under the `/raw/` prefix to any page path to get its clean markdown source — handy for piping a single page into a model: ```bash curl https://hydrasdk.com/raw/getting-started/installation.md ``` The raw endpoint is **locale-aware** — prefix the locale to fetch a translated page: ```bash curl https://hydrasdk.com/raw/vi/getting-started/installation.md curl https://hydrasdk.com/raw/ja/getting-started/installation.md ```