import { CompilationData , Compiler } from '../template/compiler-types' ; import { CompilationError , CompilationResultParseError , CompilationResultReduceError , CompilationResultResolveError , } from '../template/language/language-types' ; import { allErrorsAreRecoverable , extractResolvedVariableBytecodeMap , } from '../template/language/language-utils' ; import { encodeOutpoints , encodeOutput , encodeOutputsForSigning , encodeSequenceNumbersForSigning , } from './transaction-serialization' ; import { BytecodeGenerationCompletionInput , BytecodeGenerationCompletionOutput , BytecodeGenerationErrorBase , BytecodeGenerationErrorLocking , BytecodeGenerationErrorUnlocking , Input , InputTemplate , Output , OutputTemplate , TransactionContextCommon , TransactionGenerationAttempt , TransactionGenerationError , TransactionTemplateFixed , } from './transaction-types' ; const returnFailedCompilationDirective = < Type extends 'locking' | 'unlocking' > ( { index , result , type , } ) => { return { errors : result . errors . map ( ( error ) => ( { ... error , error : `Failed compilation of ${ type } directive at index "${ index }": ${ error . error }` , } ) ) , index , ... ( result . errorType === 'parse' ? { } : { resolved : result . resolve } ) , type , } ; } ; export const compileOutputTemplate = < CompilerType extends Compiler < TransactionContextCommon , unknown , unknown > > ( { outputTemplate , index , } ) : Output | BytecodeGenerationErrorLocking => { if ( 'script' in outputTemplate . lockingBytecode ) { const directive = outputTemplate . lockingBytecode ; const data = directive . data === undefined ? { } : directive . data ; const result = directive . compiler . generateBytecode ( directive . script , data , true ) ; return result . success ? { lockingBytecode : result . bytecode , satoshis : outputTemplate . satoshis , } : returnFailedCompilationDirective ( { index , result , type : 'locking' } ) ; } return { lockingBytecode : outputTemplate . lockingBytecode . slice ( ) , satoshis : outputTemplate . satoshis , } ; } ; export const compileInputTemplate = < CompilerType extends Compiler < TransactionContextCommon , unknown , unknown > > ( { inputTemplate , index , outputs , template , transactionOutpoints , transactionSequenceNumbers , } ) : Input | BytecodeGenerationErrorUnlocking => { if ( 'script' in inputTemplate . unlockingBytecode ) { const directive = inputTemplate . unlockingBytecode ; const correspondingOutput = outputs [ index ] as Output | undefined ; const result = directive . compiler . generateBytecode ( directive . script , { ... directive . data , transactionContext : { correspondingOutput : correspondingOutput === undefined ? undefined : encodeOutput ( correspondingOutput ) , locktime : template . locktime , outpointIndex : inputTemplate . outpointIndex , outpointTransactionHash : inputTemplate . outpointTransactionHash . slice ( ) , outputValue : directive . satoshis , sequenceNumber : inputTemplate . sequenceNumber , transactionOutpoints : transactionOutpoints . slice ( ) , transactionOutputs : encodeOutputsForSigning ( outputs ) , transactionSequenceNumbers : transactionSequenceNumbers . slice ( ) , version : template . version , } , } , true ) ; return result . success ? { outpointIndex : inputTemplate . outpointIndex , outpointTransactionHash : inputTemplate . outpointTransactionHash . slice ( ) , sequenceNumber : inputTemplate . sequenceNumber , unlockingBytecode : result . bytecode , } : returnFailedCompilationDirective ( { index , result , type : 'unlocking' } ) ; } return { outpointIndex : inputTemplate . outpointIndex , outpointTransactionHash : inputTemplate . outpointTransactionHash . slice ( ) , sequenceNumber : inputTemplate . sequenceNumber , unlockingBytecode : inputTemplate . unlockingBytecode . slice ( ) , } ; } ; param template export const generateTransaction = < CompilerType extends Compiler < TransactionContextCommon , unknown , unknown > > ( template ) : TransactionGenerationAttempt => { const outputResults = template . outputs . map ( ( outputTemplate , index ) => compileOutputTemplate ( { index , outputTemplate , } ) ) ; const outputCompilationErrors = outputResults . filter ( ( result ) : result is BytecodeGenerationErrorLocking => 'errors' in result ) ; if ( outputCompilationErrors . length > 0 ) { const outputCompletions = outputResults . map < BytecodeGenerationCompletionOutput | BytecodeGenerationErrorLocking > ( ( result , index ) => 'lockingBytecode' in result ? { index , output : result , type : 'output' } : result ) . filter ( ( result ) : result is BytecodeGenerationCompletionOutput => 'output' in result ) ; return { completions : outputCompletions , errors : outputCompilationErrors , stage : 'outputs' , success : false , } ; } const outputs = outputResults as Output [ ] ; const inputSerializationElements = template . inputs . map ( ( inputTemplate ) => ( { outpointIndex : inputTemplate . outpointIndex , outpointTransactionHash : inputTemplate . outpointTransactionHash . slice ( ) , sequenceNumber : inputTemplate . sequenceNumber , } ) ) ; const transactionOutpoints = encodeOutpoints ( inputSerializationElements ) ; const transactionSequenceNumbers = encodeSequenceNumbersForSigning ( inputSerializationElements ) ; const inputResults = template . inputs . map ( ( inputTemplate , index ) => compileInputTemplate ( { index , inputTemplate , outputs , template , transactionOutpoints , transactionSequenceNumbers , } ) ) ; const inputCompilationErrors = inputResults . filter ( ( result ) : result is BytecodeGenerationErrorUnlocking => 'errors' in result ) ; if ( inputCompilationErrors . length > 0 ) { const inputCompletions = inputResults . map < BytecodeGenerationCompletionInput | BytecodeGenerationErrorUnlocking > ( ( result , index ) => 'unlockingBytecode' in result ? { index , input : result , type : 'input' } : result ) . filter ( ( result ) : result is BytecodeGenerationCompletionInput => 'input' in result ) ; return { completions : inputCompletions , errors : inputCompilationErrors , stage : 'inputs' , success : false , } ; } const inputs = inputResults as Input [ ] ; return { success : true , transaction : { inputs , locktime : template . locktime , outputs , version : template . version , } , } ; } ; param transactionGenerationError export const extractResolvedVariables = ( transactionGenerationError ) => ( transactionGenerationError . errors as BytecodeGenerationErrorBase [ ] ) . reduce < { [ fullIdentifier ] : Uint8Array ; } > ( ( all , error ) => error . resolved === undefined ? all : { ... all , ... extractResolvedVariableBytecodeMap ( error . resolved ) } , { } ) ; param transactionGenerationError export const extractMissingVariables = ( transactionGenerationError ) => { const allErrors = ( transactionGenerationError . errors as BytecodeGenerationErrorBase [ ] ) . reduce < CompilationError [ ] > ( ( all , error ) => [ ... all , ... error . errors ] , [ ] ) ; if ( ! allErrorsAreRecoverable ( allErrors ) ) { return false ; } return allErrors . reduce < { [ fullIdentifier ] : string ; } > ( ( all , error ) => ( { ... all , [ error . missingIdentifier ] : error . owningEntity , } ) , { } ) ; } ; remarks export const safelyExtendCompilationData = < TransactionContext = TransactionContextCommon > ( transactionGenerationError , trustedCompilationData , untrustedResolutions ) : false | CompilationData < TransactionContext > => { const missing = extractMissingVariables ( transactionGenerationError ) ; if ( missing === false ) return false ; const selectedResolutions = Object . entries ( missing ) . reduce < { [ fullIdentifier ] : Uint8Array ; } > ( ( all , [ identifier , entityId ] ) => { const entityResolution = untrustedResolutions [ entityId ] as { [ fullIdentifier ] : Uint8Array ; } | undefined ; if ( entityResolution === undefined ) { return all ; } const resolution = entityResolution [ identifier ] as Uint8Array | undefined ; if ( resolution === undefined ) { return all ; } return { ... all , [ identifier ] : resolution } ; } , { } ) ; return { ... trustedCompilationData , bytecode : { ... selectedResolutions , ... trustedCompilationData . bytecode , } , } ; } ;