Transaction Signing Examples

Core transaction signing patterns based on real implementations from nodejs-playground/src.

Simple Signing

typescript
import { AppWallet, NETWORK_ID, deserializeTx } 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 = 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

typescript
import { AppWallet, NETWORK_ID, deserializeTx } 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: 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)

Error Handling Pattern

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

enum SigningErrorType {
  INVALID_CBOR = 'invalid_cbor',
  USER_REJECTED = 'user_rejected',
  NETWORK_ERROR = 'network_error'
}

class RobustSigner {
  private wallet: AppWallet
  private retryAttempts = 3
  private retryDelay = 1000

  constructor(mnemonic: string[]) {
    this.wallet = new AppWallet({
      key: { type: 'mnemonic', words: mnemonic },
      networkId: NETWORK_ID.PREPROD
    })
  }

  async signWithRetry(cborHex: string) {
    let lastError: Error

    for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
      try {
        const signedTx = await this.wallet.signTx(cborHex)
        const txHash = deserializeTx(signedTx).transaction_hash().to_hex()
        
        console.log(`✅ Signing successful on attempt ${attempt}`)
        return { signedTx, txHash, attempt }
        
      } catch (error) {
        lastError = error
        const errorType = this.classifyError(error)
        
        // Don't retry certain errors
        if (errorType === SigningErrorType.INVALID_CBOR || 
            errorType === SigningErrorType.USER_REJECTED) {
          throw error
        }
        
        if (attempt < this.retryAttempts) {
          await this.delay(this.retryDelay * attempt)
        }
      }
    }

    throw new Error(`Signing failed after ${this.retryAttempts} attempts: ${lastError.message}`)
  }

  private classifyError(error: Error): SigningErrorType {
    const message = error.message.toLowerCase()
    
    if (message.includes('invalid') && message.includes('cbor')) {
      return SigningErrorType.INVALID_CBOR
    }
    if (message.includes('rejected') || message.includes('cancelled')) {
      return SigningErrorType.USER_REJECTED
    }
    
    return SigningErrorType.NETWORK_ERROR
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms))
  }
}

// Usage
const robustSigner = new RobustSigner('your mnemonic words here'.split(' '))
const result = await robustSigner.signWithRetry(transactionCbor)

Performance Pattern

typescript
import { AppWallet, NETWORK_ID, deserializeTx } 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 = 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)