{"version":3,"file":"bitcoin.mjs","sources":["../../src/signers/http.ts","../../src/signers/bitcoin.ts"],"sourcesContent":["import type { SignerConfig } from '../types/signers';\n\nfunction sanitizeErrorMessage(status: number, text: string): string {\n  // Sanitize error messages to avoid leaking sensitive server information\n  let errorMessage = `Emblem signer error ${status}`;\n\n  if (status >= 500) {\n    errorMessage += ': Internal server error';\n  } else if (status === 401 || status === 403) {\n    errorMessage += ': Authentication failed';\n  } else if (status === 404) {\n    errorMessage += ': Resource not found';\n  } else if (status === 405) {\n    errorMessage += ': Method not allowed';\n  } else if (text) {\n    // For 4xx client errors, include limited error details\n    errorMessage += `: ${text.substring(0, 200)}`; // Limit to 200 chars\n  }\n\n  return errorMessage;\n}\n\nasync function resolveAuthHeaders(config: SignerConfig): Promise<Record<string, string>> {\n  // Priority: custom headers -> jwt/getJwt/sdk -> apiKey (deprecated)\n  if (typeof config.getAuthHeaders === 'function') {\n    const h = await config.getAuthHeaders();\n    if (h && typeof h === 'object') return h;\n  }\n\n  const tok =\n    config.jwt ??\n    (typeof config.getJwt === 'function' ? await config.getJwt() : undefined) ??\n    config.sdk?.getSession()?.authToken ??\n    undefined;\n\n  if (tok) {\n    return { Authorization: `Bearer ${tok}` };\n  }\n\n  // apiKey is deprecated but still supported as fallback\n  if (config.apiKey) {\n    return { 'x-api-key': config.apiKey };\n  }\n\n  throw new Error(\n    'No authentication available: provide jwt, getJwt(), getAuthHeaders(), sdk, or apiKey'\n  );\n}\n\nexport async function emblemPost<T = unknown>(\n  path: string,\n  body: unknown,\n  config: SignerConfig\n): Promise<T> {\n  const baseUrl = config.baseUrl ?? 'https://api.emblemvault.ai';\n  const authHeaders = await resolveAuthHeaders(config);\n  const res = await fetch(`${baseUrl}${path}`, {\n    method: 'POST',\n    headers: {\n      'content-type': 'application/json',\n      ...authHeaders,\n    },\n    body: JSON.stringify(body, (_key: string, value: unknown) =>\n      typeof value === 'bigint' ? value.toString() : value\n    ),\n  });\n\n  if (!res.ok) {\n    const text = await res.text().catch(() => '');\n    throw new Error(sanitizeErrorMessage(res.status, text));\n  }\n\n  return res.json() as Promise<T>;\n}\n\nexport async function emblemGet<T = unknown>(path: string, config: SignerConfig): Promise<T> {\n  const baseUrl = config.baseUrl ?? 'https://api.emblemvault.ai';\n  const authHeaders = await resolveAuthHeaders(config);\n  const res = await fetch(`${baseUrl}${path}`, {\n    method: 'GET',\n    headers: authHeaders,\n  });\n\n  if (!res.ok) {\n    const text = await res.text().catch(() => '');\n    throw new Error(sanitizeErrorMessage(res.status, text));\n  }\n\n  return res.json() as Promise<T>;\n}\n","import type { SignerConfig, SignerVaultInfo } from '../types/signers';\nimport { emblemPost } from './http';\nimport { fetchVaultInfo } from './vault';\n\n/**\n * Bitcoin address types\n */\nexport type BitcoinAddressType = 'p2pkh' | 'p2wpkh' | 'p2tr' | 'p2sh';\n\n/**\n * Bitcoin network type\n */\nexport type BitcoinNetwork = 'mainnet' | 'testnet';\n\n/**\n * Input signing instruction for PSBTs\n * Compatible with UniSat wallet-api's UserToSignInput format\n */\nexport interface BitcoinToSignInput {\n  /** Input index in the PSBT */\n  index: number;\n  /** Bitcoin address associated with this input */\n  address?: string;\n  /** Public key (hex) for this input */\n  publicKey?: string;\n  /** Sighash types for signing */\n  sighashTypes?: number[];\n  /** Tap leaf hash to sign (hex) for Taproot script path spending */\n  tapLeafHashToSign?: string;\n  /** Use tweaked signer for Taproot */\n  useTweakedSigner?: boolean;\n  /** Disable tweak signer for Taproot */\n  disableTweakSigner?: boolean;\n}\n\n/**\n * Bitcoin sign transaction options\n */\nexport interface BitcoinSignOptions {\n  /** Transaction type determines signing algorithm (ECDSA for p2pkh/p2wpkh, Schnorr for p2tr) */\n  transactionType: 'p2pkh' | 'p2wpkh' | 'p2tr';\n  /** Per-input signing instructions for complex PSBTs */\n  toSignInputs?: BitcoinToSignInput[];\n}\n\n/**\n * Response from signing a Bitcoin PSBT\n */\nexport interface BitcoinSignResult {\n  success: boolean;\n  /** Signed PSBT in base64 format */\n  signedPsbt: string;\n  /** Final transaction hex (ready for broadcast) */\n  signedTxHex: string;\n  /** Raw signature */\n  signature: string;\n}\n\n/**\n * Extended vault info including Bitcoin public key and derived addresses\n */\nexport interface BitcoinVaultInfo extends SignerVaultInfo {\n  /** Bitcoin public key (compressed, hex) */\n  btcPubkey: string;\n  /** Derived Bitcoin addresses */\n  btcAddresses?: {\n    /** Legacy (1...) */\n    p2pkh: string;\n    /** Native SegWit (bc1q...) */\n    p2wpkh: string;\n    /** Taproot (bc1p...) */\n    p2tr: string;\n  };\n}\n\n/**\n * Bitcoin signer interface compatible with common Bitcoin libraries\n */\nexport interface BitcoinSignerInterface {\n  /** Bitcoin public key (compressed, hex) */\n  publicKey: string;\n  /** Derived Bitcoin addresses */\n  addresses: {\n    p2pkh: string;\n    p2wpkh: string;\n    p2tr: string;\n  };\n  /** Sign a PSBT */\n  signPsbt(psbtBase64: string, options: BitcoinSignOptions): Promise<BitcoinSignResult>;\n  /** Sign multiple PSBTs */\n  signAllPsbts(\n    psbts: Array<{ psbt: string; options: BitcoinSignOptions }>\n  ): Promise<BitcoinSignResult[]>;\n  /** Get the vault ID */\n  getVaultId(): string;\n  /** Get public key as hex */\n  getPublicKey(): string;\n}\n\n/**\n * Constants for Bitcoin calculations\n */\nexport const SATS_PER_BTC = 100_000_000;\n\n/**\n * Emblem Bitcoin Signer implementation\n */\nexport class EmblemBitcoinSigner implements BitcoinSignerInterface {\n  readonly publicKey: string; // Compressed public key (hex)\n  readonly addresses: {\n    p2pkh: string;\n    p2wpkh: string;\n    p2tr: string;\n  };\n  private readonly config: SignerConfig;\n  private readonly vaultId: string;\n\n  constructor(config: SignerConfig, vaultInfo: BitcoinVaultInfo) {\n    if (!vaultInfo.btcPubkey) {\n      throw new Error('Bitcoin public key is required in vault info');\n    }\n    this.publicKey = vaultInfo.btcPubkey;\n    this.addresses = vaultInfo.btcAddresses ?? {\n      p2pkh: '',\n      p2wpkh: '',\n      p2tr: '',\n    };\n    this.config = config;\n    this.vaultId = vaultInfo.vaultId;\n  }\n\n  /**\n   * Sign a Bitcoin PSBT\n   * @param psbtBase64 - PSBT in base64 format\n   * @param options - Signing options including transaction type and per-input instructions\n   */\n  async signPsbt(psbtBase64: string, options: BitcoinSignOptions): Promise<BitcoinSignResult> {\n    const { transactionType, toSignInputs } = options;\n\n    // Normalize toSignInputs for JSON transmission\n    const normalizedInputs = toSignInputs?.map(input => ({\n      ...input,\n      // Ensure tapLeafHashToSign is a string (some libs pass Buffer/Uint8Array)\n      tapLeafHashToSign: this.normalizeToHex(input.tapLeafHashToSign),\n    }));\n\n    const response = await emblemPost<BitcoinSignResult>(\n      '/sign-btc-transaction',\n      {\n        vaultId: this.vaultId,\n        psbt: psbtBase64,\n        transactionType,\n        toSignInputs: normalizedInputs,\n      },\n      this.config\n    );\n\n    return response;\n  }\n\n  /**\n   * Sign multiple PSBTs\n   * @param psbts - Array of PSBTs with their signing options\n   */\n  async signAllPsbts(\n    psbts: Array<{ psbt: string; options: BitcoinSignOptions }>\n  ): Promise<BitcoinSignResult[]> {\n    const results: BitcoinSignResult[] = [];\n    for (const { psbt, options } of psbts) {\n      results.push(await this.signPsbt(psbt, options));\n    }\n    return results;\n  }\n\n  /**\n   * Get the vault ID\n   */\n  getVaultId(): string {\n    return this.vaultId;\n  }\n\n  /**\n   * Get public key as hex\n   */\n  getPublicKey(): string {\n    return this.publicKey;\n  }\n\n  /**\n   * Check if a given address belongs to this signer\n   */\n  ownsAddress(address: string): boolean {\n    return (\n      address === this.addresses.p2pkh ||\n      address === this.addresses.p2wpkh ||\n      address === this.addresses.p2tr\n    );\n  }\n\n  /**\n   * Get the appropriate address for a given type\n   */\n  getAddress(type: BitcoinAddressType = 'p2wpkh'): string {\n    switch (type) {\n      case 'p2pkh':\n        return this.addresses.p2pkh;\n      case 'p2wpkh':\n        return this.addresses.p2wpkh;\n      case 'p2tr':\n        return this.addresses.p2tr;\n      case 'p2sh':\n        // P2SH is typically wrapped SegWit, return p2wpkh as fallback\n        return this.addresses.p2wpkh;\n      default:\n        return this.addresses.p2wpkh;\n    }\n  }\n\n  /**\n   * Normalize a value to hex string (handles string, Buffer, Uint8Array)\n   */\n  private normalizeToHex(value: string | ArrayLike<number> | undefined): string | undefined {\n    if (value === undefined || value === null) return undefined;\n    if (typeof value === 'string') return value;\n    // Handle Buffer/Uint8Array\n    const bytes = value as ArrayLike<number>;\n    let hex = '';\n    for (let i = 0; i < bytes.length; i++) {\n      hex += (bytes[i] as number).toString(16).padStart(2, '0');\n    }\n    return hex;\n  }\n}\n\n/**\n * Create an Emblem Bitcoin signer from config\n * @param config - Signer configuration with authentication\n * @param infoOverride - Optional vault info to skip API call\n */\nexport async function toBitcoinSigner(\n  config: SignerConfig,\n  infoOverride?: BitcoinVaultInfo\n): Promise<EmblemBitcoinSigner> {\n  const info = infoOverride ?? (await fetchBitcoinVaultInfo(config));\n  return new EmblemBitcoinSigner(config, info);\n}\n\n/**\n * Fetch vault info with Bitcoin-specific fields\n */\nexport async function fetchBitcoinVaultInfo(config: SignerConfig): Promise<BitcoinVaultInfo> {\n  const data = await emblemPost<{\n    vaultId: string;\n    address?: string;\n    evmAddress?: string;\n    btcPubkey?: string;\n    btcAddresses?: {\n      p2pkh: string;\n      p2wpkh: string;\n      p2tr: string;\n    };\n    created_by?: string;\n  }>('/vault/info', {}, config);\n\n  if (!data || !data.vaultId) {\n    throw new Error('Invalid vault info response: missing vaultId');\n  }\n\n  if (!data.btcPubkey) {\n    throw new Error('Invalid vault info response: missing btcPubkey (Bitcoin public key)');\n  }\n\n  return {\n    vaultId: data.vaultId,\n    address: data.address || '',\n    evmAddress: (data.evmAddress || '0x') as `0x${string}`,\n    btcPubkey: data.btcPubkey,\n    btcAddresses: data.btcAddresses,\n    created_by: data.created_by,\n  };\n}\n\n/**\n * Utility: Detect Bitcoin address type from address string\n */\nexport function detectAddressType(address: string): BitcoinAddressType | 'unknown' {\n  if (address.startsWith('bc1p')) return 'p2tr'; // Taproot (bech32m)\n  if (address.startsWith('bc1q')) return 'p2wpkh'; // Native SegWit (bech32)\n  if (address.startsWith('1')) return 'p2pkh'; // Legacy\n  if (address.startsWith('3')) return 'p2sh'; // Script Hash\n  // Testnet addresses\n  if (address.startsWith('tb1p')) return 'p2tr';\n  if (address.startsWith('tb1q')) return 'p2wpkh';\n  if (address.startsWith('m') || address.startsWith('n')) return 'p2pkh';\n  if (address.startsWith('2')) return 'p2sh';\n  return 'unknown';\n}\n\n/**\n * Utility: Convert satoshis to BTC\n */\nexport function satsToBTC(sats: number): number {\n  return sats / SATS_PER_BTC;\n}\n\n/**\n * Utility: Convert BTC to satoshis\n */\nexport function btcToSats(btc: number): number {\n  return Math.round(btc * SATS_PER_BTC);\n}\n\n/**\n * Utility: Format satoshis for display\n */\nexport function formatSats(sats: number): string {\n  return `${sats.toLocaleString()} sats (${satsToBTC(sats).toFixed(8)} BTC)`;\n}\n\n/**\n * Utility: Estimate transaction size in vbytes\n */\nexport function estimateTransactionSize(\n  inputCount: number,\n  outputCount: number,\n  inputType: BitcoinAddressType = 'p2wpkh'\n): number {\n  let inputSize: number;\n  switch (inputType) {\n    case 'p2pkh':\n      inputSize = 148; // Legacy (no witness discount)\n      break;\n    case 'p2tr':\n      inputSize = 58; // Taproot (approx vbytes)\n      break;\n    case 'p2wpkh':\n    default:\n      inputSize = 68; // Native SegWit (approx vbytes)\n      break;\n  }\n\n  const outputSize = 31; // Bech32 output (approx vbytes)\n  const overhead = 10; // Version, locktime, etc.\n\n  return overhead + inputCount * inputSize + outputCount * outputSize;\n}\n\n/**\n * Utility: Calculate estimated fee\n */\nexport function calculateFee(vsize: number, feeRate: number): number {\n  return Math.ceil(vsize * feeRate);\n}\n\n/**\n * Utility: Check if amount is above dust threshold\n */\nexport function isDust(sats: number, addressType: BitcoinAddressType = 'p2wpkh'): boolean {\n  // Dust threshold: ~546 sats for legacy, ~294 for SegWit\n  const dustThreshold = addressType === 'p2wpkh' || addressType === 'p2tr' ? 294 : 546;\n  return sats < dustThreshold;\n}\n"],"names":["async","emblemPost","path","body","config","baseUrl","authHeaders","getAuthHeaders","h","tok","jwt","getJwt","undefined","sdk","getSession","authToken","Authorization","apiKey","Error","resolveAuthHeaders","res","fetch","method","headers","JSON","stringify","_key","value","toString","ok","text","catch","status","errorMessage","substring","sanitizeErrorMessage","json","SATS_PER_BTC","EmblemBitcoinSigner","constructor","vaultInfo","btcPubkey","this","publicKey","addresses","btcAddresses","p2pkh","p2wpkh","p2tr","vaultId","signPsbt","psbtBase64","options","transactionType","toSignInputs","normalizedInputs","map","input","tapLeafHashToSign","normalizeToHex","psbt","signAllPsbts","psbts","results","push","getVaultId","getPublicKey","ownsAddress","address","getAddress","type","bytes","hex","i","length","padStart","toBitcoinSigner","infoOverride","info","fetchBitcoinVaultInfo","data","evmAddress","created_by","detectAddressType","startsWith","satsToBTC","sats","btcToSats","btc","Math","round","formatSats","toLocaleString","toFixed","estimateTransactionSize","inputCount","outputCount","inputType","inputSize","calculateFee","vsize","feeRate","ceil","isDust","addressType"],"mappings":"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"}