UNPKG

@btc-vision/transaction

Version:

OPNet transaction library allows you to create and sign transactions for the OPNet network.

369 lines 14 kB
import { toHex } from '@btc-vision/bitcoin'; import { ChainId } from '../../network/ChainId.js'; import { currentConsensus } from '../../consensus/ConsensusConfig.js'; import { TransactionType } from '../enums/TransactionType.js'; import { SERIALIZATION_FORMAT_VERSION } from './interfaces/ISerializableState.js'; /** * Captures transaction state from builders for offline signing. * This class creates serializable state objects from transaction parameters. */ export class TransactionStateCapture { /** * Capture state from a FundingTransaction */ static fromFunding(params, precomputed) { return this.captureState({ params, type: TransactionType.FUNDING, ...(precomputed !== undefined ? { precomputed } : {}), }); } /** * Capture state from a DeploymentTransaction */ static fromDeployment(params, precomputed) { return this.captureState({ params: params, type: TransactionType.DEPLOYMENT, precomputed, }); } /** * Capture state from an InteractionTransaction */ static fromInteraction(params, precomputed) { return this.captureState({ params, type: TransactionType.INTERACTION, precomputed, }); } /** * Capture state from a MultiSignTransaction */ static fromMultiSig(params, precomputed) { return this.captureState({ params, type: TransactionType.MULTI_SIG, ...(precomputed !== undefined ? { precomputed } : {}), }); } /** * Capture state from a CustomScriptTransaction */ static fromCustomScript(params, precomputed) { return this.captureState({ params, type: TransactionType.CUSTOM_CODE, ...(precomputed !== undefined ? { precomputed } : {}), }); } /** * Capture state from a CancelTransaction */ static fromCancel(params, precomputed) { return this.captureState({ params, type: TransactionType.CANCEL, ...(precomputed !== undefined ? { precomputed } : {}), }); } /** * Main state capture method */ static captureState(capture) { const { params, type, precomputed } = capture; return { header: this.createHeader(type, params.network, params.chainId), baseParams: this.extractBaseParams(params), utxos: this.serializeUTXOs(params.utxos), optionalInputs: this.serializeUTXOs(params.optionalInputs || []), optionalOutputs: this.serializeOutputs(params.optionalOutputs || []), addressRotationEnabled: params.addressRotation?.enabled ?? false, signerMappings: this.extractSignerMappings(params), typeSpecificData: this.extractTypeSpecificData(type, params), precomputedData: this.buildPrecomputedData(precomputed), }; } /** * Create serialization header */ static createHeader(type, network, chainId) { return { formatVersion: SERIALIZATION_FORMAT_VERSION, consensusVersion: currentConsensus, transactionType: type, chainId: chainId ?? this.networkToChainId(network), timestamp: Date.now(), }; } /** * Extract base parameters common to all transaction types */ static extractBaseParams(params) { const note = params.note ? params.note instanceof Uint8Array ? toHex(params.note) : toHex(new TextEncoder().encode(params.note)) : undefined; // Handle optional priorityFee and gasSatFee (not present in MultiSig) const priorityFee = params.priorityFee ?? 0n; const gasSatFee = params.gasSatFee ?? 0n; return { from: params.from || '', feeRate: params.feeRate, priorityFee: priorityFee.toString(), gasSatFee: gasSatFee.toString(), networkName: this.networkToName(params.network), txVersion: params.txVersion ?? 2, anchor: params.anchor ?? false, ...(params.to !== undefined ? { to: params.to } : {}), ...(note !== undefined ? { note } : {}), ...(params.debugFees !== undefined ? { debugFees: params.debugFees } : {}), }; } /** * Extract signer mappings for address rotation mode */ static extractSignerMappings(params) { if (!params.addressRotation?.enabled) { return []; } const mappings = []; const addressToIndices = new Map(); // Build mapping from UTXOs params.utxos.forEach((utxo, index) => { const address = utxo.scriptPubKey?.address; if (address) { const existing = addressToIndices.get(address); if (existing) { existing.push(index); } else { addressToIndices.set(address, [index]); } } }); // Add optional inputs const utxoCount = params.utxos.length; (params.optionalInputs || []).forEach((utxo, index) => { const address = utxo.scriptPubKey?.address; if (address) { const existing = addressToIndices.get(address); if (existing) { existing.push(utxoCount + index); } else { addressToIndices.set(address, [utxoCount + index]); } } }); // Convert to serializable format addressToIndices.forEach((indices, address) => { mappings.push({ address, inputIndices: indices }); }); return mappings; } /** * Extract type-specific data based on transaction type */ static extractTypeSpecificData(type, params) { switch (type) { case TransactionType.FUNDING: return this.extractFundingData(params); case TransactionType.DEPLOYMENT: return this.extractDeploymentData(params); case TransactionType.INTERACTION: return this.extractInteractionData(params); case TransactionType.MULTI_SIG: return this.extractMultiSigData(params); case TransactionType.CUSTOM_CODE: return this.extractCustomScriptData(params); case TransactionType.CANCEL: return this.extractCancelData(params); default: throw new Error(`Unsupported transaction type: ${type}`); } } static extractFundingData(params) { return { type: TransactionType.FUNDING, amount: params.amount.toString(), splitInputsInto: params.splitInputsInto ?? 1, }; } static extractDeploymentData(params) { return { type: TransactionType.DEPLOYMENT, bytecode: toHex(params.bytecode), challenge: params.challenge.toRaw(), ...(params.calldata ? { calldata: toHex(params.calldata) } : {}), ...(params.revealMLDSAPublicKey !== undefined ? { revealMLDSAPublicKey: params.revealMLDSAPublicKey } : {}), ...(params.linkMLDSAPublicKeyToAddress !== undefined ? { linkMLDSAPublicKeyToAddress: params.linkMLDSAPublicKeyToAddress } : {}), }; } static extractInteractionData(params) { return { type: TransactionType.INTERACTION, calldata: toHex(params.calldata), challenge: params.challenge.toRaw(), ...(params.contract !== undefined ? { contract: params.contract } : {}), ...(params.loadedStorage !== undefined ? { loadedStorage: params.loadedStorage } : {}), ...(params.isCancellation !== undefined ? { isCancellation: params.isCancellation } : {}), ...(params.disableAutoRefund !== undefined ? { disableAutoRefund: params.disableAutoRefund } : {}), ...(params.revealMLDSAPublicKey !== undefined ? { revealMLDSAPublicKey: params.revealMLDSAPublicKey } : {}), ...(params.linkMLDSAPublicKeyToAddress !== undefined ? { linkMLDSAPublicKeyToAddress: params.linkMLDSAPublicKeyToAddress } : {}), }; } static extractMultiSigData(params) { return { type: TransactionType.MULTI_SIG, pubkeys: (params.pubkeys || []).map((pk) => toHex(pk)), minimumSignatures: params.minimumSignatures || 0, receiver: params.receiver || '', requestedAmount: (params.requestedAmount || 0n).toString(), refundVault: params.refundVault || '', originalInputCount: params.originalInputCount || params.utxos.length, ...(params.existingPsbtBase64 !== undefined ? { existingPsbtBase64: params.existingPsbtBase64 } : {}), }; } static extractCustomScriptData(params) { const scriptElements = (params.scriptElements || []).map((element) => { if (element instanceof Uint8Array) { return { elementType: 'buffer', value: toHex(element), }; } else { return { elementType: 'opcode', value: element, }; } }); return { type: TransactionType.CUSTOM_CODE, scriptElements, witnesses: (params.witnesses || []).map((w) => toHex(w)), ...(params.annex ? { annex: toHex(params.annex) } : {}), }; } static extractCancelData(params) { const script = params.compiledTargetScript; const scriptHex = script ? (script instanceof Uint8Array ? toHex(script) : script) : ''; return { type: TransactionType.CANCEL, compiledTargetScript: scriptHex, }; } /** * Build precomputed data object */ static buildPrecomputedData(precomputed) { return { ...(precomputed?.compiledTargetScript !== undefined ? { compiledTargetScript: precomputed.compiledTargetScript } : {}), ...(precomputed?.randomBytes !== undefined ? { randomBytes: precomputed.randomBytes } : {}), ...(precomputed?.estimatedFees !== undefined ? { estimatedFees: precomputed.estimatedFees } : {}), ...(precomputed?.contractSeed !== undefined ? { contractSeed: precomputed.contractSeed } : {}), ...(precomputed?.contractAddress !== undefined ? { contractAddress: precomputed.contractAddress } : {}), }; } /** * Serialize UTXOs array */ static serializeUTXOs(utxos) { return utxos.map((utxo) => { const redeemScript = utxo.redeemScript ? utxo.redeemScript instanceof Uint8Array ? toHex(utxo.redeemScript) : utxo.redeemScript : undefined; const witnessScript = utxo.witnessScript ? utxo.witnessScript instanceof Uint8Array ? toHex(utxo.witnessScript) : utxo.witnessScript : undefined; const nonWitnessUtxo = utxo.nonWitnessUtxo ? utxo.nonWitnessUtxo instanceof Uint8Array ? toHex(utxo.nonWitnessUtxo) : utxo.nonWitnessUtxo : undefined; return { transactionId: utxo.transactionId, outputIndex: utxo.outputIndex, value: utxo.value.toString(), scriptPubKeyHex: utxo.scriptPubKey.hex, ...(utxo.scriptPubKey.address !== undefined ? { scriptPubKeyAddress: utxo.scriptPubKey.address } : {}), ...(redeemScript !== undefined ? { redeemScript } : {}), ...(witnessScript !== undefined ? { witnessScript } : {}), ...(nonWitnessUtxo !== undefined ? { nonWitnessUtxo } : {}), }; }); } /** * Serialize outputs array */ static serializeOutputs(outputs) { return outputs.map((output) => { const address = 'address' in output ? output.address : undefined; const script = 'script' in output ? output.script : undefined; const scriptHex = script ? toHex(script) : undefined; const tapInternalKeyHex = output.tapInternalKey ? toHex(output.tapInternalKey) : undefined; return { value: Number(output.value), ...(address !== undefined ? { address } : {}), ...(scriptHex !== undefined ? { script: scriptHex } : {}), ...(tapInternalKeyHex !== undefined ? { tapInternalKey: tapInternalKeyHex } : {}), }; }); } /** * Convert network to name string */ static networkToName(network) { if (network.bech32 === 'bc') return 'mainnet'; if (network.bech32 === 'tb') return 'testnet'; if (network.bech32 === 'opt') return 'opnetTestnet'; return 'regtest'; } /** * Convert network to chain ID */ static networkToChainId(_network) { // Default to Bitcoin chain return ChainId.Bitcoin; } } //# sourceMappingURL=TransactionStateCapture.js.map