import { getEnumDescription } from '../helpers' ; import { StacksMessageParsingError , NotImplementedError } from '../errors' ; import { ClarityValue , deserializeCV , BufferReader , serializeCV } from '@stacks/transactions' ; const MICROBLOCK_HEADER_SIZE = 1 + 2 + 32 + 32 + 65 ; export enum SigHashMode { /** SingleSigHashMode */ P2PKH = 0x00 , /** SingleSigHashMode */ P2WPKH = 0x02 , /** MultiSigHashMode */ P2SH = 0x01 , /** MultiSigHashMode */ P2WSH = 0x03 } export enum TransactionPublicKeyEncoding { Compressed = 0x00 , Uncompressed = 0x01 } interface TransactionSpendingConditionSingleSig { hashMode : SigHashMode . P2PKH | SigHashMode . P2WPKH ; signer : Buffer ; nonce : bigint ; feeRate : bigint ; keyEncoding : TransactionPublicKeyEncoding ; signature : Buffer ; } enum TransactionAuthFieldTypeID { PublicKeyCompressed = 0x00 , PublicKeyUncompressed = 0x01 , SignatureCompressed = 0x02 , SignatureUncompressed = 0x03 } interface TransactionAuthFieldPublicKey { typeId : TransactionAuthFieldTypeID . PublicKeyCompressed | TransactionAuthFieldTypeID . PublicKeyUncompressed ; publicKey : Buffer ; } interface TransactionAuthFieldSignature { typeId : TransactionAuthFieldTypeID . SignatureCompressed | TransactionAuthFieldTypeID . SignatureUncompressed ; signature : Buffer ; } type TransactionAuthField = TransactionAuthFieldPublicKey | TransactionAuthFieldSignature ; interface TransactionSpendingConditionMultiSig { hashMode : SigHashMode . P2SH | SigHashMode . P2WSH ; signer : Buffer ; nonce : bigint ; feeRate : bigint ; authFields : TransactionAuthField [ ] ; /** A 2-byte signature count indicating the number of signatures that are required for the authorization to be valid */ signatureCount : number ; } export enum TransactionAuthTypeID { Standard = 0x04 , Sponsored = 0x05 } type TransactionSpendingCondition = TransactionSpendingConditionSingleSig | TransactionSpendingConditionMultiSig ; interface TransactionAuthStandard { typeId : TransactionAuthTypeID . Standard ; originCondition : TransactionSpendingCondition ; } interface TransactionAuthSponsored { typeId : TransactionAuthTypeID . Sponsored ; originCondition : TransactionSpendingCondition ; sponsorCondition : TransactionSpendingCondition ; } export enum TransactionAnchorMode { /** must be included in a StacksBlock */ OnChainOnly = 1 , /** must be included in a StacksMicroBlock */ OffChainOnly = 2 , /** either */ Any = 3 } export enum TransactionPostConditionMode { Allow = 0x01 , Deny = 0x02 } enum TransactionVersion { Mainnet = 0x00 , Testnet = 0x80 } export enum AssetInfoTypeID { STX = 0 , FungibleAsset = 1 , NonfungibleAsset = 2 } export enum PostConditionPrincipalTypeID { Origin = 0x01 , Standard = 0x02 , Contract = 0x03 } interface PostConditionPrincipalOrigin { typeId : PostConditionPrincipalTypeID . Origin ; } interface PostConditionPrincipalStandard { typeId : PostConditionPrincipalTypeID . Standard ; address : StacksAddress ; } interface PostConditionPrincipalContract { typeId : PostConditionPrincipalTypeID . Contract ; address : StacksAddress ; contractName : string ; } export type PostConditionPrincipal = PostConditionPrincipalOrigin | PostConditionPrincipalStandard | PostConditionPrincipalContract ; export const enum RecipientPrincipalTypeId { Address = 0x05 , Contract = 0x06 } interface RecipientPrincipalAddress { typeId : RecipientPrincipalTypeId . Address ; address : StacksAddress ; } interface RecipientPrincipalContract { typeId : RecipientPrincipalTypeId . Contract ; address : StacksAddress ; contractName : string ; } type RecipientPrincipal = RecipientPrincipalAddress | RecipientPrincipalContract ; export enum FungibleConditionCode { SentEq = 0x01 , SentGt = 0x02 , SentGe = 0x03 , SentLt = 0x04 , SentLe = 0x05 } export enum NonfungibleConditionCode { Sent = 0x10 , NotSent = 0x11 } interface StacksAddress { version : number ; bytes : Buffer ; } export interface AssetInfo { contractAddress : StacksAddress ; contractName : string ; assetName : string ; } interface TransactionPostConditionStx { assetInfoId : AssetInfoTypeID . STX ; principal : PostConditionPrincipal ; conditionCode : FungibleConditionCode ; amount : bigint ; } interface TransactionPostConditionFungible { assetInfoId : AssetInfoTypeID . FungibleAsset ; principal : PostConditionPrincipal ; asset : AssetInfo ; conditionCode : FungibleConditionCode ; amount : bigint ; } interface TransactionPostConditionNonfungible { assetInfoId : AssetInfoTypeID . NonfungibleAsset ; principal : PostConditionPrincipal ; asset : AssetInfo ; assetValue : ClarityValue ; conditionCode : NonfungibleConditionCode ; } export type TransactionPostCondition = TransactionPostConditionStx | TransactionPostConditionFungible | TransactionPostConditionNonfungible ; export enum TransactionPayloadTypeID { TokenTransfer = 0 , SmartContract = 1 , ContractCall = 2 , PoisonMicroblock = 3 , Coinbase = 4 } interface TransactionPayloadTokenTransfer { typeId : TransactionPayloadTypeID . TokenTransfer ; recipient : RecipientPrincipal ; amount : bigint ; memo : Buffer ; } interface TransactionPayloadCoinbase { typeId : TransactionPayloadTypeID . Coinbase ; payload : Buffer ; } interface TransactionPayloadContractCall { typeId : TransactionPayloadTypeID . ContractCall ; address : StacksAddress ; contractName : string ; functionName : string ; functionArgs : ClarityValue [ ] ; rawFunctionArgs ? : Buffer ; } interface TransactionPayloadSmartContract { typeId : TransactionPayloadTypeID . SmartContract ; name : string ; codeBody : string ; } interface TransactionPayloadPoisonMicroblock { typeId : TransactionPayloadTypeID . PoisonMicroblock ; microblockHeader1 : Buffer ; microblockHeader2 : Buffer ; } type TransactionPayload = TransactionPayloadTokenTransfer | TransactionPayloadCoinbase | TransactionPayloadContractCall | TransactionPayloadSmartContract | TransactionPayloadPoisonMicroblock ; export interface Transaction { version : TransactionVersion ; chainId : number ; auth : TransactionAuthStandard | TransactionAuthSponsored ; anchorMode : TransactionAnchorMode ; postConditionMode : TransactionPostConditionMode ; postConditions : TransactionPostCondition [ ] ; rawPostConditions : Buffer ; payload : TransactionPayload ; } export function readTransaction ( reader ) { const version = reader . readUInt8Enum ( TransactionVersion , n => { throw new StacksMessageParsingError ( `unexpected transactions version: ${ n }` ) ; } ) ; const chainId = reader . readUInt32BE ( ) ; const authType = reader . readUInt8Enum ( TransactionAuthTypeID , n => { throw new StacksMessageParsingError ( `unexpected transaction auth type: ${ n }` ) ; } ) ; let auth ; if ( authType === TransactionAuthTypeID . Standard ) { const originCondition = readTransactionSpendingCondition ( reader ) ; const txAuth = { typeId : authType , originCondition : originCondition , } ; auth = txAuth ; } else if ( authType === TransactionAuthTypeID . Sponsored ) { const originCondition = readTransactionSpendingCondition ( reader ) ; const sponsorCondition = readTransactionSpendingCondition ( reader ) ; const txAuth = { typeId : authType , originCondition : originCondition , sponsorCondition : sponsorCondition , } ; auth = txAuth ; } else { throw new NotImplementedError ( `tx auth type: ${ getEnumDescription ( TransactionAuthTypeID , authType ) }` ) ; } const anchorMode = reader . readUInt8Enum ( TransactionAnchorMode , n => { throw new StacksMessageParsingError ( `unexpected tx post condition anchor mode: ${ n }` ) ; } ) ; const postConditionIndexStart = reader . readOffset ; const postConditionMode = reader . readUInt8Enum ( TransactionPostConditionMode , n => { throw new StacksMessageParsingError ( `unexpected tx post condition mode: ${ n }` ) ; } ) ; const postConditions = readTransactionPostConditions ( reader ) ; const rawPostConditions = Buffer . alloc ( reader . readOffset - postConditionIndexStart ) ; reader . internalBuffer . copy ( rawPostConditions , 0 , postConditionIndexStart , reader . readOffset ) ; const txPayload = readTransactionPayload ( reader ) ; const tx = { version : version , chainId : chainId , auth : auth , anchorMode : anchorMode , postConditionMode : postConditionMode , postConditions : postConditions , rawPostConditions : rawPostConditions , payload : txPayload , } ; return tx ; } function readTransactionPayload ( reader ) { const txPayloadType = reader . readUInt8Enum ( TransactionPayloadTypeID , n => { throw new StacksMessageParsingError ( `unexpected tx payload type: ${ n }` ) ; } ) ; if ( txPayloadType === TransactionPayloadTypeID . Coinbase ) { const payload = { typeId : txPayloadType , payload : reader . readBuffer ( 32 ) , } ; return payload ; } else if ( txPayloadType === TransactionPayloadTypeID . TokenTransfer ) { const recipientTypeId = reader . readUInt8 ( ) ; let recipientPrincipal ; if ( recipientTypeId === 0x05 ) { recipientPrincipal = { typeId : recipientTypeId , address : readStacksAddress ( reader ) } ; } else if ( recipientTypeId === 0x06 ) { recipientPrincipal = { typeId : recipientTypeId , address : readStacksAddress ( reader ) , contractName : readContractName ( reader ) , } ; } else { throw new Error ( `Unexpected recipient principal type ID ${ recipientTypeId }` ) ; } const payload = { typeId : txPayloadType , recipient : recipientPrincipal , amount : reader . readBigUInt64BE ( ) , memo : reader . readBuffer ( 34 ) , } ; return payload ; } else if ( txPayloadType === TransactionPayloadTypeID . SmartContract ) { const payload = { typeId : txPayloadType , name : readContractName ( reader ) , codeBody : readString ( reader ) , } ; return payload ; } else if ( txPayloadType === TransactionPayloadTypeID . ContractCall ) { const address = readStacksAddress ( reader ) ; const contractName = readContractName ( reader ) ; const functionName = readClarityName ( reader ) ; const functionArgsIndexStart = reader . readOffset ; const functionArgs = readClarityValueArray ( reader ) ; let rawFunctionArgs ; if ( functionArgs . length > 0 ) { rawFunctionArgs = Buffer . alloc ( reader . readOffset - functionArgsIndexStart ) ; reader . internalBuffer . copy ( rawFunctionArgs , 0 , functionArgsIndexStart , reader . readOffset ) ; } const payload = { typeId : txPayloadType , address : address , contractName : contractName , functionName : functionName , functionArgs : functionArgs , rawFunctionArgs : rawFunctionArgs , } ; return payload ; } else if ( txPayloadType === TransactionPayloadTypeID . PoisonMicroblock ) { const payload = { typeId : txPayloadType , microblockHeader1 : reader . readBuffer ( MICROBLOCK_HEADER_SIZE ) , microblockHeader2 : reader . readBuffer ( MICROBLOCK_HEADER_SIZE ) , } ; return payload ; } else { throw new NotImplementedError ( `tx payload type: ${ getEnumDescription ( TransactionPayloadTypeID , txPayloadType ) }` ) ; } } function readClarityValue ( reader ) { const remainingBuffer = reader . internalBuffer . slice ( reader . readOffset ) ; const bufferReader = new BufferReader ( remainingBuffer ) ; const clarityVal = deserializeCV ( bufferReader ) ; reader . readOffset += bufferReader . readOffset ; return clarityVal ; } export function readClarityValueArray ( input ) { const reader = input instanceof BufferReader ? input : BufferReader . fromBuffer ( input ) ; const valueCount = reader . readUInt32BE ( ) ; const values = new Array < ClarityValue > ( valueCount ) ; const remainingBuffer = reader . internalBuffer . slice ( reader . readOffset ) ; const bufferReader = new BufferReader ( remainingBuffer ) ; for ( let i = 0 ; i < valueCount ; i ++ ) { const clarityVal = deserializeCV ( bufferReader ) ; values [ i ] = clarityVal ; } reader . readOffset += bufferReader . readOffset ; return values ; } export function createClarityValueArray ( ... input ) { const buffers = new Array < Buffer > ( input . length ) ; for ( let i = 0 ; i < input . length ; i ++ ) { buffers [ i ] = serializeCV ( input [ i ] ) ; } const valueCountBuffer = Buffer . alloc ( 4 ) ; valueCountBuffer . writeUInt32BE ( input . length ) ; buffers . unshift ( valueCountBuffer ) ; return Buffer . concat ( buffers ) ; } function readString ( reader ) { const length = reader . readUInt32BE ( ) ; const str = reader . readString ( length , 'ascii' ) ; return str ; } function readContractName ( reader ) { const length = reader . readUInt8 ( ) ; const name = reader . readString ( length , 'ascii' ) ; return name ; } function readClarityName ( reader ) { const length = reader . readUInt8 ( ) ; const name = reader . readString ( length , 'ascii' ) ; return name ; } function readStacksAddress ( reader ) { const address = { version : reader . readUInt8 ( ) , bytes : reader . readBuffer ( 20 ) , } ; return address ; } function readAssetInfo ( reader ) { const assetInfo = { contractAddress : readStacksAddress ( reader ) , contractName : readContractName ( reader ) , assetName : readClarityName ( reader ) , } ; return assetInfo ; } export function readTransactionPostConditions ( reader ) { const conditionCount = reader . readUInt32BE ( ) ; const conditions = new Array < TransactionPostCondition > ( conditionCount ) ; for ( let i = 0 ; i < conditionCount ; i ++ ) { const typeId = reader . readUInt8Enum ( AssetInfoTypeID , n => { throw new StacksMessageParsingError ( `unexpected tx asset info type: ${ n }` ) ; } ) ; const principal = readTransactionPostConditionPrincipal ( reader ) ; if ( typeId === AssetInfoTypeID . STX ) { const condition = { assetInfoId : typeId , principal : principal , conditionCode : reader . readUInt8Enum ( FungibleConditionCode , n => { throw new StacksMessageParsingError ( `unexpected condition code: ${ n }` ) ; } ) , amount : reader . readBigUInt64BE ( ) , } ; conditions [ i ] = condition ; } else if ( typeId === AssetInfoTypeID . FungibleAsset ) { const condition = { assetInfoId : typeId , principal : principal , asset : readAssetInfo ( reader ) , conditionCode : reader . readUInt8Enum ( FungibleConditionCode , n => { throw new StacksMessageParsingError ( `unexpected condition code: ${ n }` ) ; } ) , amount : reader . readBigUInt64BE ( ) , } ; conditions [ i ] = condition ; } else if ( typeId === AssetInfoTypeID . NonfungibleAsset ) { const condition = { assetInfoId : typeId , principal : principal , asset : readAssetInfo ( reader ) , assetValue : readClarityValue ( reader ) , conditionCode : reader . readUInt8Enum ( NonfungibleConditionCode , n => { throw new StacksMessageParsingError ( `unexpected nonfungible condition code: ${ n }` ) ; } ) , } ; conditions [ i ] = condition ; } else { throw new NotImplementedError ( `tx asset info type ${ getEnumDescription ( AssetInfoTypeID , typeId ) }` ) ; } } return conditions ; } function readTransactionPostConditionPrincipal ( reader ) { const typeId = reader . readUInt8Enum ( PostConditionPrincipalTypeID , n => { throw new StacksMessageParsingError ( `unexpected tx post condition principal type: ${ n }` ) ; } ) ; if ( typeId === PostConditionPrincipalTypeID . Origin ) { const principal = { typeId : typeId , } ; return principal ; } else if ( typeId === PostConditionPrincipalTypeID . Standard ) { const principal = { typeId : typeId , address : readStacksAddress ( reader ) , } ; return principal ; } else if ( typeId === PostConditionPrincipalTypeID . Contract ) { const address = readStacksAddress ( reader ) ; const contractName = readContractName ( reader ) ; const principal = { typeId : typeId , address : address , contractName : contractName , } ; return principal ; } else { throw new NotImplementedError ( `tx post condition principal type: ${ getEnumDescription ( PostConditionPrincipalTypeID , typeId ) }` ) ; } } function readTransactionSpendingCondition ( reader ) { const conditionType = reader . readUInt8Enum ( SigHashMode , n => { throw new StacksMessageParsingError ( `unexpected tx spend condition hash mode: ${ n }` ) ; } ) ; if ( conditionType === SigHashMode . P2PKH || conditionType === SigHashMode . P2WPKH ) { const condition = { hashMode : conditionType , signer : reader . readBuffer ( 20 ) , nonce : reader . readBigUInt64BE ( ) , feeRate : reader . readBigUInt64BE ( ) , keyEncoding : reader . readUInt8 ( ) , signature : reader . readBuffer ( 65 ) , } ; return condition ; } else if ( conditionType === SigHashMode . P2SH || conditionType === SigHashMode . P2WSH ) { const condition = { hashMode : conditionType , signer : reader . readBuffer ( 20 ) , nonce : reader . readBigUInt64BE ( ) , feeRate : reader . readBigUInt64BE ( ) , authFields : new Array < TransactionAuthField > ( reader . readUInt32BE ( ) ) , signatureCount : - 1 , } ; for ( let i = 0 ; i < condition . authFields . length ; i ++ ) { const authType = reader . readUInt8Enum ( TransactionAuthFieldTypeID , n => { throw new StacksMessageParsingError ( `unexpected tx auth field type: ${ n }` ) ; } ) ; if ( authType === TransactionAuthFieldTypeID . PublicKeyCompressed || authType === TransactionAuthFieldTypeID . PublicKeyUncompressed ) { const authFieldPubkey = { typeId : authType , publicKey : reader . readBuffer ( 33 ) , } ; condition . authFields [ i ] = authFieldPubkey ; } else if ( authType === TransactionAuthFieldTypeID . SignatureCompressed || authType === TransactionAuthFieldTypeID . SignatureUncompressed ) { const authFieldSig = { typeId : authType , signature : reader . readBuffer ( 65 ) , } ; condition . authFields [ i ] = authFieldSig ; } else { throw new NotImplementedError ( `tx auth field type: ${ getEnumDescription ( TransactionAuthFieldTypeID , authType ) }` ) ; } } condition . signatureCount = reader . readUInt16BE ( ) ; return condition ; } else { throw new NotImplementedError ( `tx spend condition hash mode: ${ getEnumDescription ( SigHashMode , conditionType ) }` ) ; } }