import { Buffer , IntegerType , intToBigInt } from '@stacks/common' ; import { AnchorMode , AuthType , ChainID , DEFAULT_CHAIN_ID , PayloadType , PostConditionMode , PubKeyEncoding , StacksMessageType , TransactionVersion , } from './constants' ; import { Authorization , deserializeAuthorization , intoInitialSighashAuth , isSingleSig , nextSignature , serializeAuthorization , setFee , setNonce , setSponsor , setSponsorNonce , SpendingConditionOpts , verifyOrigin , } from './authorization' ; import { createTransactionAuthField } from './signature' ; import { BufferArray , cloneDeep , txidFromData } from './utils' ; import { deserializePayload , Payload , PayloadInput , serializePayload } from './payload' ; import { createLPList , deserializeLPList , LengthPrefixedList , serializeLPList } from './types' ; import { isCompressed , StacksPrivateKey , StacksPublicKey } from './keys' ; import { BufferReader } from './bufferReader' ; import { SerializationError , SigningError } from './errors' ; export class StacksTransaction { version : TransactionVersion ; chainId : ChainID ; auth : Authorization ; anchorMode : AnchorMode ; payload : Payload ; postConditionMode : PostConditionMode ; postConditions : LengthPrefixedList ; constructor ( version , auth , payload , postConditions ? , postConditionMode ? , anchorMode ? , chainId ? ) { this . version = version ; this . auth = auth ; if ( 'amount' in payload ) { this . payload = { ... payload , amount : intToBigInt ( payload . amount , false ) , } ; } else { this . payload = payload ; } this . chainId = chainId ?? DEFAULT_CHAIN_ID ; this . postConditionMode = postConditionMode ?? PostConditionMode . Deny ; this . postConditions = postConditions ?? createLPList ( [ ] ) ; if ( anchorMode ) { this . anchorMode = anchorMode ; } else { switch ( payload . payloadType ) { case PayloadType . Coinbase : case PayloadType . PoisonMicroblock : { this . anchorMode = AnchorMode . OnChainOnly ; break ; } case PayloadType . ContractCall : case PayloadType . SmartContract : case PayloadType . TokenTransfer : { this . anchorMode = AnchorMode . Any ; break ; } } } } signBegin ( ) { const tx = cloneDeep ( this ) ; tx . auth = intoInitialSighashAuth ( tx . auth ) ; return tx . txid ( ) ; } verifyBegin ( ) { const tx = cloneDeep ( this ) ; tx . auth = intoInitialSighashAuth ( tx . auth ) ; return tx . txid ( ) ; } verifyOrigin ( ) { return verifyOrigin ( this . auth , this . verifyBegin ( ) ) ; } signNextOrigin ( sigHash , privateKey ) { if ( this . auth . spendingCondition === undefined ) { throw new Error ( '"auth.spendingCondition" is undefined' ) ; } if ( this . auth . authType === undefined ) { throw new Error ( '"auth.authType" is undefined' ) ; } return this . signAndAppend ( this . auth . spendingCondition , sigHash , AuthType . Standard , privateKey ) ; } signNextSponsor ( sigHash , privateKey ) { if ( this . auth . authType === AuthType . Sponsored ) { return this . signAndAppend ( this . auth . sponsorSpendingCondition , sigHash , AuthType . Sponsored , privateKey ) ; } else { throw new Error ( '"auth.sponsorSpendingCondition" is undefined' ) ; } } appendPubkey ( publicKey ) { const cond = this . auth . spendingCondition ; if ( cond && ! isSingleSig ( cond ) ) { const compressed = isCompressed ( publicKey ) ; cond . fields . push ( createTransactionAuthField ( compressed ? PubKeyEncoding . Compressed : PubKeyEncoding . Uncompressed , publicKey ) ) ; } else { throw new Error ( `Can't append public key to a singlesig condition` ) ; } } signAndAppend ( condition , curSigHash , authType , privateKey ) { const { nextSig , nextSigHash } = nextSignature ( curSigHash , authType , condition . fee , condition . nonce , privateKey ) ; if ( isSingleSig ( condition ) ) { condition . signature = nextSig ; } else { const compressed = privateKey . data . toString ( 'hex' ) . endsWith ( '01' ) ; condition . fields . push ( createTransactionAuthField ( compressed ? PubKeyEncoding . Compressed : PubKeyEncoding . Uncompressed , nextSig ) ) ; } return nextSigHash ; } txid ( ) { const serialized = this . serialize ( ) ; return txidFromData ( serialized ) ; } setSponsor ( sponsorSpendingCondition ) { if ( this . auth . authType != AuthType . Sponsored ) { throw new SigningError ( 'Cannot sponsor sign a non-sponsored transaction' ) ; } this . auth = setSponsor ( this . auth , sponsorSpendingCondition ) ; } param fee setFee ( amount ) { this . auth = setFee ( this . auth , amount ) ; } param nonce setNonce ( nonce ) { this . auth = setNonce ( this . auth , nonce ) ; } param nonce setSponsorNonce ( nonce ) { if ( this . auth . authType != AuthType . Sponsored ) { throw new SigningError ( 'Cannot sponsor sign a non-sponsored transaction' ) ; } this . auth = setSponsorNonce ( this . auth , nonce ) ; } serialize ( ) { if ( this . version === undefined ) { throw new SerializationError ( '"version" is undefined' ) ; } if ( this . chainId === undefined ) { throw new SerializationError ( '"chainId" is undefined' ) ; } if ( this . auth === undefined ) { throw new SerializationError ( '"auth" is undefined' ) ; } if ( this . anchorMode === undefined ) { throw new SerializationError ( '"anchorMode" is undefined' ) ; } if ( this . payload === undefined ) { throw new SerializationError ( '"payload" is undefined' ) ; } const bufferArray = new BufferArray ( ) ; bufferArray . appendByte ( this . version ) ; const chainIdBuffer = Buffer . alloc ( 4 ) ; chainIdBuffer . writeUInt32BE ( this . chainId , 0 ) ; bufferArray . push ( chainIdBuffer ) ; bufferArray . push ( serializeAuthorization ( this . auth ) ) ; bufferArray . appendByte ( this . anchorMode ) ; bufferArray . appendByte ( this . postConditionMode ) ; bufferArray . push ( serializeLPList ( this . postConditions ) ) ; bufferArray . push ( serializePayload ( this . payload ) ) ; return bufferArray . concatBuffer ( ) ; } } param data export function deserializeTransaction ( data ) { let bufferReader ; if ( typeof data === 'string' ) { if ( data . slice ( 0 , 2 ) . toLowerCase ( ) === '0x' ) { bufferReader = new BufferReader ( Buffer . from ( data . slice ( 2 ) , 'hex' ) ) ; } else { bufferReader = new BufferReader ( Buffer . from ( data , 'hex' ) ) ; } } else if ( Buffer . isBuffer ( data ) ) { bufferReader = new BufferReader ( data ) ; } else { bufferReader = data ; } const version = bufferReader . readUInt8Enum ( TransactionVersion , n => { throw new Error ( `Could not parse ${ n } as TransactionVersion` ) ; } ) ; const chainId = bufferReader . readUInt32BE ( ) ; const auth = deserializeAuthorization ( bufferReader ) ; const anchorMode = bufferReader . readUInt8Enum ( AnchorMode , n => { throw new Error ( `Could not parse ${ n } as AnchorMode` ) ; } ) ; const postConditionMode = bufferReader . readUInt8Enum ( PostConditionMode , n => { throw new Error ( `Could not parse ${ n } as PostConditionMode` ) ; } ) ; const postConditions = deserializeLPList ( bufferReader , StacksMessageType . PostCondition ) ; const payload = deserializePayload ( bufferReader ) ; return new StacksTransaction ( version , auth , payload , postConditions , postConditionMode , anchorMode , chainId ) ; }