github.com/hashgraph/hedera-sdk-go/v2@v2.48.0/transaction_record.go (about)

     1  package hedera
     2  
     3  /*-
     4   *
     5   * Hedera Go SDK
     6   *
     7   * Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC
     8   *
     9   * Licensed under the Apache License, Version 2.0 (the "License");
    10   * you may not use this file except in compliance with the License.
    11   * You may obtain a copy of the License at
    12   *
    13   *      http://www.apache.org/licenses/LICENSE-2.0
    14   *
    15   * Unless required by applicable law or agreed to in writing, software
    16   * distributed under the License is distributed on an "AS IS" BASIS,
    17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    18   * See the License for the specific language governing permissions and
    19   * limitations under the License.
    20   *
    21   */
    22  
    23  import (
    24  	"encoding/hex"
    25  	"fmt"
    26  	"sort"
    27  	"time"
    28  
    29  	jsoniter "github.com/json-iterator/go"
    30  	"google.golang.org/protobuf/types/known/wrapperspb"
    31  
    32  	protobuf "google.golang.org/protobuf/proto"
    33  
    34  	"github.com/hashgraph/hedera-protobufs-go/services"
    35  )
    36  
    37  // The complete record for a transaction on Hedera that has reached consensus.
    38  // This is not-free to request and is available for 1 hour after a transaction reaches consensus.
    39  type TransactionRecord struct {
    40  	Receipt                    TransactionReceipt
    41  	TransactionHash            []byte
    42  	ConsensusTimestamp         time.Time
    43  	TransactionID              TransactionID
    44  	TransactionMemo            string
    45  	TransactionFee             Hbar
    46  	Transfers                  []Transfer
    47  	TokenTransfers             map[TokenID][]TokenTransfer
    48  	NftTransfers               map[TokenID][]_TokenNftTransfer
    49  	ExpectedDecimals           map[TokenID]uint32
    50  	CallResult                 *ContractFunctionResult
    51  	CallResultIsCreate         bool
    52  	AssessedCustomFees         []AssessedCustomFee
    53  	AutomaticTokenAssociations []TokenAssociation
    54  	ParentConsensusTimestamp   time.Time
    55  	AliasKey                   *PublicKey
    56  	Duplicates                 []TransactionRecord
    57  	Children                   []TransactionRecord
    58  	// Deprecated
    59  	HbarAllowances []HbarAllowance
    60  	// Deprecated
    61  	TokenAllowances []TokenAllowance
    62  	// Deprecated
    63  	TokenNftAllowances    []TokenNftAllowance
    64  	EthereumHash          []byte
    65  	PaidStakingRewards    map[AccountID]Hbar
    66  	PrngBytes             []byte
    67  	PrngNumber            *int32
    68  	EvmAddress            []byte
    69  	PendingAirdropRecords []PendingAirdropRecord
    70  }
    71  
    72  // MarshalJSON returns the JSON representation of the TransactionRecord
    73  func (record TransactionRecord) MarshalJSON() ([]byte, error) {
    74  	var json = jsoniter.ConfigCompatibleWithStandardLibrary
    75  	m := make(map[string]interface{})
    76  	type TransferJSON struct {
    77  		AccountID  string      `json:"accountId"`
    78  		Amount     interface{} `json:"amount"`
    79  		IsApproved bool        `json:"isApproved"`
    80  	}
    81  	var transfersJSON []TransferJSON
    82  	for _, t := range record.Transfers {
    83  		transfersJSON = append(transfersJSON, TransferJSON{
    84  			AccountID:  t.AccountID.String(),
    85  			Amount:     fmt.Sprint(t.Amount.AsTinybar()),
    86  			IsApproved: t.IsApproved,
    87  		})
    88  	}
    89  
    90  	// It's weird because we need it without field names to match the specification
    91  	tokenTransfersMap := make(map[string]map[string]string)
    92  
    93  	for tokenId, transfers := range record.TokenTransfers {
    94  		accountIdMap := make(map[string]string)
    95  		for _, transfer := range transfers {
    96  			accountIdMap[transfer.AccountID.String()] = fmt.Sprint(transfer.Amount)
    97  		}
    98  		tokenTransfersMap[tokenId.String()] = accountIdMap
    99  	}
   100  
   101  	type TransferNftTokensJSON struct {
   102  		SenderAccountID            string `json:"sender"`
   103  		ReceiverAccountIDAccountID string `json:"recipient"`
   104  		IsApproved                 bool   `json:"isApproved"`
   105  		SerialNumber               int64  `json:"serial"`
   106  	}
   107  	var transfersNftTokenJSON []TransferNftTokensJSON
   108  	tokenTransfersNftMap := make(map[string][]TransferNftTokensJSON)
   109  	for k, v := range record.NftTransfers {
   110  		for _, tt := range v {
   111  			transfersNftTokenJSON = append(transfersNftTokenJSON, TransferNftTokensJSON{
   112  				SenderAccountID:            tt.SenderAccountID.String(),
   113  				ReceiverAccountIDAccountID: tt.ReceiverAccountID.String(),
   114  				IsApproved:                 tt.IsApproved,
   115  				SerialNumber:               tt.SerialNumber,
   116  			})
   117  		}
   118  		tokenTransfersNftMap[k.String()] = transfersNftTokenJSON
   119  	}
   120  
   121  	m["transactionHash"] = hex.EncodeToString(record.TransactionHash)
   122  	m["transactionId"] = record.TransactionID.String()
   123  	m["transactionMemo"] = record.TransactionMemo
   124  	m["transactionFee"] = fmt.Sprint(record.TransactionFee.AsTinybar())
   125  	m["transfers"] = transfersJSON
   126  	m["tokenTransfers"] = tokenTransfersMap
   127  	m["nftTransfers"] = tokenTransfersNftMap
   128  	m["expectedDecimals"] = record.ExpectedDecimals
   129  	m["callResultIsCreate"] = record.CallResultIsCreate
   130  
   131  	type AssessedCustomFeeJSON struct {
   132  		FeeCollectorAccountID string   `json:"feeCollectorAccountId"`
   133  		TokenID               string   `json:"tokenId"`
   134  		Amount                string   `json:"amount"`
   135  		PayerAccountIDs       []string `json:"payerAccountIds"`
   136  	}
   137  
   138  	customFeesJSON := make([]AssessedCustomFeeJSON, len(record.AssessedCustomFees))
   139  
   140  	for i, fee := range record.AssessedCustomFees {
   141  		payerAccountIDsStr := make([]string, len(fee.PayerAccountIDs))
   142  		for j, accID := range fee.PayerAccountIDs {
   143  			payerAccountIDsStr[j] = accID.String()
   144  		}
   145  		customFeesJSON[i] = AssessedCustomFeeJSON{
   146  			FeeCollectorAccountID: fee.FeeCollectorAccountId.String(),
   147  			TokenID:               fee.TokenID.String(),
   148  			Amount:                fmt.Sprint(fee.Amount),
   149  			PayerAccountIDs:       payerAccountIDsStr,
   150  		}
   151  	}
   152  
   153  	m["assessedCustomFees"] = customFeesJSON
   154  
   155  	type TokenAssociationOutput struct {
   156  		TokenID   string `json:"tokenId"`
   157  		AccountID string `json:"accountId"`
   158  	}
   159  	automaticTokenAssociations := make([]TokenAssociationOutput, len(record.AutomaticTokenAssociations))
   160  	for i, ta := range record.AutomaticTokenAssociations {
   161  		automaticTokenAssociations[i] = TokenAssociationOutput{
   162  			TokenID:   ta.TokenID.String(),
   163  			AccountID: ta.AccountID.String(),
   164  		}
   165  	}
   166  	m["automaticTokenAssociations"] = automaticTokenAssociations
   167  
   168  	consensusTime := record.ConsensusTimestamp.UTC().Format("2006-01-02T15:04:05.000Z")
   169  	parentConsesnusTime := record.ParentConsensusTimestamp.UTC().Format("2006-01-02T15:04:05.000Z")
   170  	m["consensusTimestamp"] = consensusTime
   171  	m["parentConsensusTimestamp"] = parentConsesnusTime
   172  
   173  	m["aliasKey"] = fmt.Sprint(record.AliasKey)
   174  	m["ethereumHash"] = hex.EncodeToString(record.EthereumHash)
   175  	type PaidStakingReward struct {
   176  		AccountID  string `json:"accountId"`
   177  		Amount     string `json:"amount"`
   178  		IsApproved bool   `json:"isApproved"`
   179  	}
   180  	var paidStakingRewards []PaidStakingReward
   181  	for k, reward := range record.PaidStakingRewards {
   182  		paidStakingReward := PaidStakingReward{
   183  			AccountID:  k.String(),
   184  			Amount:     fmt.Sprint(reward.AsTinybar()),
   185  			IsApproved: false,
   186  		}
   187  		paidStakingRewards = append(paidStakingRewards, paidStakingReward)
   188  	}
   189  
   190  	sort.Slice(paidStakingRewards, func(i, j int) bool {
   191  		return paidStakingRewards[i].AccountID < paidStakingRewards[j].AccountID
   192  	})
   193  
   194  	m["paidStakingRewards"] = paidStakingRewards
   195  
   196  	m["prngBytes"] = hex.EncodeToString(record.PrngBytes)
   197  	m["prngNumber"] = record.PrngNumber
   198  	m["evmAddress"] = hex.EncodeToString(record.EvmAddress)
   199  	m["receipt"] = record.Receipt
   200  	m["children"] = record.Children
   201  	m["duplicates"] = record.Duplicates
   202  
   203  	type PendingAirdropIdOutput struct {
   204  		Sender   string `json:"sender"`
   205  		Receiver string `json:"receiver"`
   206  		TokenID  string `json:"tokenId"`
   207  		NftID    string `json:"nftId"`
   208  	}
   209  	type PendingAirdropsOutput struct {
   210  		PendingAirdropId     PendingAirdropIdOutput `json:"pendingAirdropId"`
   211  		PendingAirdropAmount string                 `json:"pendingAirdropAmount"`
   212  	}
   213  	pendingAirdropRecords := make([]PendingAirdropsOutput, len(record.PendingAirdropRecords))
   214  	for i, p := range record.PendingAirdropRecords {
   215  		var tokenID string
   216  		var nftID string
   217  		var sender string
   218  		var receiver string
   219  		if p.pendingAirdropId.tokenID != nil {
   220  			tokenID = p.pendingAirdropId.tokenID.String()
   221  		}
   222  		if p.pendingAirdropId.nftID != nil {
   223  			nftID = p.pendingAirdropId.nftID.String()
   224  		}
   225  		if p.pendingAirdropId.sender != nil {
   226  			sender = p.pendingAirdropId.sender.String()
   227  		}
   228  		if p.pendingAirdropId.receiver != nil {
   229  			receiver = p.pendingAirdropId.receiver.String()
   230  		}
   231  		pendingAirdropRecords[i] = PendingAirdropsOutput{
   232  			PendingAirdropId: PendingAirdropIdOutput{
   233  				Sender:   sender,
   234  				Receiver: receiver,
   235  				TokenID:  tokenID,
   236  				NftID:    nftID,
   237  			},
   238  			PendingAirdropAmount: fmt.Sprint(p.pendingAirdropAmount),
   239  		}
   240  	}
   241  	m["pendingAirdropRecords"] = pendingAirdropRecords
   242  
   243  	receiptBytes, err := record.Receipt.MarshalJSON()
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	var receiptInterface interface{}
   248  	err = json.Unmarshal(receiptBytes, &receiptInterface)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	m["receipt"] = receiptInterface
   253  
   254  	result, err := json.Marshal(m)
   255  	return result, err
   256  }
   257  
   258  // GetContractExecuteResult returns the ContractFunctionResult if the transaction was a contract call
   259  func (record TransactionRecord) GetContractExecuteResult() (ContractFunctionResult, error) {
   260  	if record.CallResult == nil || record.CallResultIsCreate {
   261  		return ContractFunctionResult{}, fmt.Errorf("record does not contain a contract execute result")
   262  	}
   263  
   264  	return *record.CallResult, nil
   265  }
   266  
   267  // GetContractCreateResult returns the ContractFunctionResult if the transaction was a contract create
   268  func (record TransactionRecord) GetContractCreateResult() (ContractFunctionResult, error) {
   269  	if record.CallResult == nil || !record.CallResultIsCreate {
   270  		return ContractFunctionResult{}, fmt.Errorf("record does not contain a contract create result")
   271  	}
   272  
   273  	return *record.CallResult, nil
   274  }
   275  
   276  func _TransactionRecordFromProtobuf(protoResponse *services.TransactionGetRecordResponse, txID *TransactionID) TransactionRecord {
   277  	if protoResponse == nil {
   278  		return TransactionRecord{}
   279  	}
   280  	pb := protoResponse.GetTransactionRecord()
   281  	if pb == nil {
   282  		return TransactionRecord{}
   283  	}
   284  	var accountTransfers = make([]Transfer, 0)
   285  	var tokenTransfers = make(map[TokenID][]TokenTransfer)
   286  	var nftTransfers = make(map[TokenID][]_TokenNftTransfer)
   287  	var expectedDecimals = make(map[TokenID]uint32)
   288  
   289  	if pb.TransferList != nil {
   290  		for _, element := range pb.TransferList.AccountAmounts {
   291  			accountTransfers = append(accountTransfers, _TransferFromProtobuf(element))
   292  		}
   293  	}
   294  
   295  	for _, tokenTransfer := range pb.TokenTransferLists {
   296  		for _, nftTransfer := range tokenTransfer.NftTransfers {
   297  			if token := _TokenIDFromProtobuf(tokenTransfer.Token); token != nil {
   298  				nftTransfers[*token] = append(nftTransfers[*token], _NftTransferFromProtobuf(nftTransfer))
   299  			}
   300  		}
   301  
   302  		for _, accountAmount := range tokenTransfer.Transfers {
   303  			if token := _TokenIDFromProtobuf(tokenTransfer.Token); token != nil {
   304  				tokenTransfers[*token] = append(tokenTransfers[*token], _TokenTransferFromProtobuf(accountAmount))
   305  			}
   306  		}
   307  
   308  		if tokenTransfer.ExpectedDecimals != nil {
   309  			if token := _TokenIDFromProtobuf(tokenTransfer.Token); token != nil {
   310  				expectedDecimals[*token] = tokenTransfer.ExpectedDecimals.Value
   311  			}
   312  		}
   313  	}
   314  
   315  	assessedCustomFees := make([]AssessedCustomFee, 0)
   316  	for _, fee := range pb.AssessedCustomFees {
   317  		assessedCustomFees = append(assessedCustomFees, _AssessedCustomFeeFromProtobuf(fee))
   318  	}
   319  
   320  	tokenAssociation := make([]TokenAssociation, 0)
   321  	for _, association := range pb.AutomaticTokenAssociations {
   322  		tokenAssociation = append(tokenAssociation, tokenAssociationFromProtobuf(association))
   323  	}
   324  
   325  	paidStakingRewards := make(map[AccountID]Hbar)
   326  	for _, aa := range pb.PaidStakingRewards {
   327  		account := _AccountIDFromProtobuf(aa.AccountID)
   328  		if val, ok := paidStakingRewards[*account]; ok {
   329  			paidStakingRewards[*account] = HbarFromTinybar(val.tinybar + aa.Amount)
   330  		}
   331  
   332  		paidStakingRewards[*account] = HbarFromTinybar(aa.Amount)
   333  	}
   334  
   335  	var alias *PublicKey
   336  	if len(pb.Alias) != 0 {
   337  		pbKey := services.Key{}
   338  		_ = protobuf.Unmarshal(pb.Alias, &pbKey)
   339  		initialKey, _ := _KeyFromProtobuf(&pbKey)
   340  		switch t2 := initialKey.(type) { //nolint
   341  		case PublicKey:
   342  			alias = &t2
   343  		}
   344  	}
   345  
   346  	childReceipts := make([]TransactionRecord, 0)
   347  	if len(protoResponse.ChildTransactionRecords) > 0 {
   348  		for _, r := range protoResponse.ChildTransactionRecords {
   349  			childReceipts = append(childReceipts, _TransactionRecordFromProtobuf(&services.TransactionGetRecordResponse{TransactionRecord: r}, txID))
   350  		}
   351  	}
   352  
   353  	duplicateReceipts := make([]TransactionRecord, 0)
   354  	if len(protoResponse.DuplicateTransactionRecords) > 0 {
   355  		for _, r := range protoResponse.DuplicateTransactionRecords {
   356  			duplicateReceipts = append(duplicateReceipts, _TransactionRecordFromProtobuf(&services.TransactionGetRecordResponse{TransactionRecord: r}, txID))
   357  		}
   358  	}
   359  
   360  	var transactionID TransactionID
   361  	if pb.TransactionID != nil {
   362  		transactionID = _TransactionIDFromProtobuf(pb.TransactionID)
   363  	}
   364  
   365  	var pendingAirdropRecords []PendingAirdropRecord
   366  	for _, pendingAirdropRecord := range pb.NewPendingAirdrops {
   367  		pendingAirdropRecords = append(pendingAirdropRecords, _PendingAirdropRecordFromProtobuf(pendingAirdropRecord))
   368  	}
   369  
   370  	txRecord := TransactionRecord{
   371  		Receipt:                    _TransactionReceiptFromProtobuf(&services.TransactionGetReceiptResponse{Receipt: pb.GetReceipt()}, txID),
   372  		TransactionHash:            pb.TransactionHash,
   373  		ConsensusTimestamp:         _TimeFromProtobuf(pb.ConsensusTimestamp),
   374  		TransactionID:              transactionID,
   375  		TransactionMemo:            pb.Memo,
   376  		TransactionFee:             HbarFromTinybar(int64(pb.TransactionFee)),
   377  		Transfers:                  accountTransfers,
   378  		TokenTransfers:             tokenTransfers,
   379  		NftTransfers:               nftTransfers,
   380  		CallResultIsCreate:         true,
   381  		AssessedCustomFees:         assessedCustomFees,
   382  		AutomaticTokenAssociations: tokenAssociation,
   383  		ParentConsensusTimestamp:   _TimeFromProtobuf(pb.ParentConsensusTimestamp),
   384  		AliasKey:                   alias,
   385  		Duplicates:                 duplicateReceipts,
   386  		Children:                   childReceipts,
   387  		EthereumHash:               pb.EthereumHash,
   388  		PaidStakingRewards:         paidStakingRewards,
   389  		EvmAddress:                 pb.EvmAddress,
   390  		PendingAirdropRecords:      pendingAirdropRecords,
   391  	}
   392  
   393  	if w, ok := pb.Entropy.(*services.TransactionRecord_PrngBytes); ok {
   394  		txRecord.PrngBytes = w.PrngBytes
   395  	}
   396  
   397  	if w, ok := pb.Entropy.(*services.TransactionRecord_PrngNumber); ok {
   398  		txRecord.PrngNumber = &w.PrngNumber
   399  	}
   400  
   401  	if pb.GetContractCreateResult() != nil {
   402  		result := _ContractFunctionResultFromProtobuf(pb.GetContractCreateResult())
   403  
   404  		txRecord.CallResult = &result
   405  	} else if pb.GetContractCallResult() != nil {
   406  		result := _ContractFunctionResultFromProtobuf(pb.GetContractCallResult())
   407  
   408  		txRecord.CallResult = &result
   409  		txRecord.CallResultIsCreate = false
   410  	}
   411  
   412  	return txRecord
   413  }
   414  
   415  func (record TransactionRecord) _ToProtobuf() (*services.TransactionGetRecordResponse, error) {
   416  	var amounts = make([]*services.AccountAmount, 0)
   417  	for _, amount := range record.Transfers {
   418  		amounts = append(amounts, &services.AccountAmount{
   419  			AccountID: amount.AccountID._ToProtobuf(),
   420  			Amount:    amount.Amount.tinybar,
   421  		})
   422  	}
   423  
   424  	var transferList = services.TransferList{
   425  		AccountAmounts: amounts,
   426  	}
   427  
   428  	var tokenTransfers = make([]*services.TokenTransferList, 0)
   429  
   430  	for tokenID, tokenTransfer := range record.TokenTransfers {
   431  		tokenTemp := make([]*services.AccountAmount, 0)
   432  
   433  		for _, accountAmount := range tokenTransfer {
   434  			tokenTemp = append(tokenTemp, accountAmount._ToProtobuf())
   435  		}
   436  
   437  		bod := &services.TokenTransferList{
   438  			Token:     tokenID._ToProtobuf(),
   439  			Transfers: tokenTemp,
   440  		}
   441  
   442  		if decimal, ok := record.ExpectedDecimals[tokenID]; ok {
   443  			bod.ExpectedDecimals = &wrapperspb.UInt32Value{Value: decimal}
   444  		}
   445  
   446  		tokenTransfers = append(tokenTransfers, bod)
   447  	}
   448  
   449  	for tokenID, nftTransfers := range record.NftTransfers {
   450  		nftTemp := make([]*services.NftTransfer, 0)
   451  
   452  		for _, nftTransfer := range nftTransfers {
   453  			nftTemp = append(nftTemp, nftTransfer._ToProtobuf())
   454  		}
   455  
   456  		tokenTransfers = append(tokenTransfers, &services.TokenTransferList{
   457  			Token:        tokenID._ToProtobuf(),
   458  			NftTransfers: nftTemp,
   459  		})
   460  	}
   461  
   462  	assessedCustomFees := make([]*services.AssessedCustomFee, 0)
   463  	for _, fee := range record.AssessedCustomFees {
   464  		assessedCustomFees = append(assessedCustomFees, fee._ToProtobuf())
   465  	}
   466  
   467  	tokenAssociation := make([]*services.TokenAssociation, 0)
   468  	for _, association := range record.AutomaticTokenAssociations {
   469  		tokenAssociation = append(tokenAssociation, association.toProtobuf())
   470  	}
   471  
   472  	var alias []byte
   473  	if record.AliasKey != nil {
   474  		alias, _ = protobuf.Marshal(record.AliasKey._ToProtoKey())
   475  	}
   476  
   477  	paidStakingRewards := make([]*services.AccountAmount, 0)
   478  	for account, hbar := range record.PaidStakingRewards {
   479  		paidStakingRewards = append(paidStakingRewards, &services.AccountAmount{
   480  			AccountID: account._ToProtobuf(),
   481  			Amount:    hbar.AsTinybar(),
   482  		})
   483  	}
   484  
   485  	var tRecord = services.TransactionRecord{
   486  		Receipt:         record.Receipt._ToProtobuf().GetReceipt(),
   487  		TransactionHash: record.TransactionHash,
   488  		ConsensusTimestamp: &services.Timestamp{
   489  			Seconds: int64(record.ConsensusTimestamp.Second()),
   490  			Nanos:   int32(record.ConsensusTimestamp.Nanosecond()),
   491  		},
   492  		TransactionID:              record.TransactionID._ToProtobuf(),
   493  		Memo:                       record.TransactionMemo,
   494  		TransactionFee:             uint64(record.TransactionFee.AsTinybar()),
   495  		TransferList:               &transferList,
   496  		TokenTransferLists:         tokenTransfers,
   497  		AssessedCustomFees:         assessedCustomFees,
   498  		AutomaticTokenAssociations: tokenAssociation,
   499  		ParentConsensusTimestamp: &services.Timestamp{
   500  			Seconds: int64(record.ParentConsensusTimestamp.Second()),
   501  			Nanos:   int32(record.ParentConsensusTimestamp.Nanosecond()),
   502  		},
   503  		Alias:              alias,
   504  		EthereumHash:       record.EthereumHash,
   505  		PaidStakingRewards: paidStakingRewards,
   506  		EvmAddress:         record.EvmAddress,
   507  	}
   508  
   509  	if record.PrngNumber != nil {
   510  		tRecord.Entropy = &services.TransactionRecord_PrngNumber{PrngNumber: *record.PrngNumber}
   511  	} else if len(record.PrngBytes) > 0 {
   512  		tRecord.Entropy = &services.TransactionRecord_PrngBytes{PrngBytes: record.PrngBytes}
   513  	}
   514  
   515  	var err error
   516  	if record.CallResultIsCreate {
   517  		var choice, err = record.GetContractCreateResult()
   518  
   519  		if err != nil {
   520  			return nil, err
   521  		}
   522  
   523  		tRecord.Body = &services.TransactionRecord_ContractCreateResult{
   524  			ContractCreateResult: choice._ToProtobuf(),
   525  		}
   526  	} else {
   527  		var choice, err = record.GetContractExecuteResult()
   528  
   529  		if err != nil {
   530  			return nil, err
   531  		}
   532  
   533  		tRecord.Body = &services.TransactionRecord_ContractCallResult{
   534  			ContractCallResult: choice._ToProtobuf(),
   535  		}
   536  	}
   537  
   538  	childReceipts := make([]*services.TransactionRecord, 0)
   539  	if len(record.Children) > 0 {
   540  		for _, r := range record.Children {
   541  			temp, err := r._ToProtobuf()
   542  			if err != nil {
   543  				return nil, err
   544  			}
   545  			childReceipts = append(childReceipts, temp.GetTransactionRecord())
   546  		}
   547  	}
   548  
   549  	duplicateReceipts := make([]*services.TransactionRecord, 0)
   550  	if len(record.Duplicates) > 0 {
   551  		for _, r := range record.Duplicates {
   552  			temp, err := r._ToProtobuf()
   553  			if err != nil {
   554  				return nil, err
   555  			}
   556  			duplicateReceipts = append(duplicateReceipts, temp.GetTransactionRecord())
   557  		}
   558  	}
   559  
   560  	if record.PendingAirdropRecords != nil {
   561  		for _, pendingAirdropRecord := range record.PendingAirdropRecords {
   562  			tRecord.NewPendingAirdrops = append(tRecord.NewPendingAirdrops, pendingAirdropRecord._ToProtobuf())
   563  		}
   564  	}
   565  
   566  	return &services.TransactionGetRecordResponse{
   567  		TransactionRecord:           &tRecord,
   568  		ChildTransactionRecords:     childReceipts,
   569  		DuplicateTransactionRecords: duplicateReceipts,
   570  	}, err
   571  }
   572  
   573  // Validate checks that the receipt status is Success
   574  func (record TransactionRecord) ValidateReceiptStatus(shouldValidate bool) error {
   575  	return record.Receipt.ValidateStatus(shouldValidate)
   576  }
   577  
   578  // ToBytes returns the serialized bytes of a TransactionRecord
   579  func (record TransactionRecord) ToBytes() []byte {
   580  	rec, err := record._ToProtobuf()
   581  	if err != nil {
   582  		return make([]byte, 0)
   583  	}
   584  	data, err := protobuf.Marshal(rec)
   585  	if err != nil {
   586  		return make([]byte, 0)
   587  	}
   588  
   589  	return data
   590  }
   591  
   592  // TransactionRecordFromBytes returns a TransactionRecord from a raw protobuf byte array
   593  func TransactionRecordFromBytes(data []byte) (TransactionRecord, error) {
   594  	if data == nil {
   595  		return TransactionRecord{}, errByteArrayNull
   596  	}
   597  	pb := services.TransactionGetRecordResponse{}
   598  	err := protobuf.Unmarshal(data, &pb)
   599  	if err != nil {
   600  		return TransactionRecord{}, err
   601  	}
   602  
   603  	return _TransactionRecordFromProtobuf(&pb, nil), nil
   604  }