@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
369 lines • 14 kB
JavaScript
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