From f8cfb0492171c92385aa34c19bde4d8a526143a0 Mon Sep 17 00:00:00 2001 From: Emanuel Pargov Date: Tue, 17 Oct 2023 11:49:18 +0300 Subject: [PATCH] Feat: Add ToJSON on recepit, record, response Signed-off-by: Emanuel Pargov --- account_id_e2e_test.go | 1 - go.mod | 1 + go.sum | 3 + transaction_receipt.go | 63 ++++++++ transaction_receipt_query_unit_test.go | 192 +++++++++++++++++++++++++ transaction_record.go | 162 ++++++++++++++++++++- transaction_record_query_unit_test.go | 88 ++++++++++++ transaction_response.go | 16 +++ transfer.go | 3 + 9 files changed, 526 insertions(+), 3 deletions(-) diff --git a/account_id_e2e_test.go b/account_id_e2e_test.go index 9159b42da..64204bc86 100644 --- a/account_id_e2e_test.go +++ b/account_id_e2e_test.go @@ -46,7 +46,6 @@ func TestIntegrationAccountIDCanPopulateAccountNumber(t *testing.T) { receipt, err := tx.GetReceiptQuery().SetIncludeChildren(true).Execute(env.Client) require.NoError(t, err) newAccountId := *receipt.Children[0].AccountID - idMirror, err := AccountIDFromEvmPublicAddress(evmAddress) require.NoError(t, err) time.Sleep(5 * time.Second) diff --git a/go.mod b/go.mod index 76158d7cc..47384aa98 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/ethereum/go-ethereum v1.13.2 github.com/getsentry/sentry-go v0.24.1 // indirect github.com/hashgraph/hedera-protobufs-go v0.2.1-0.20230720072335-ed5726877e99 + github.com/json-iterator/go v1.1.12 github.com/pkg/errors v0.9.1 github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect diff --git a/go.sum b/go.sum index d586b72fd..3d3bba1cf 100644 --- a/go.sum +++ b/go.sum @@ -1356,6 +1356,7 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -1512,9 +1513,11 @@ github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iP github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= diff --git a/transaction_receipt.go b/transaction_receipt.go index d90a02d80..13c3bc9ed 100644 --- a/transaction_receipt.go +++ b/transaction_receipt.go @@ -21,6 +21,12 @@ package hedera */ import ( + "encoding/hex" + "encoding/json" + "fmt" + "reflect" + "time" + "github.com/hashgraph/hedera-protobufs-go/services" protobuf "google.golang.org/protobuf/proto" ) @@ -46,6 +52,63 @@ type TransactionReceipt struct { TransactionID *TransactionID } +func (receipt *TransactionReceipt) _ToMap() map[string]interface{} { + m := map[string]interface{}{ + "status": receipt.Status.String(), + "topicSequenceNumber": receipt.TopicSequenceNumber, + "topicRunningHash": hex.EncodeToString(receipt.TopicRunningHash), + "topicRunningHashVersion": receipt.TopicRunningHashVersion, + "totalSupply": receipt.TotalSupply, + "serialNumbers": receipt.SerialNumbers, + } + + // The real ExchangeRate struct has cents and ExpirationTime fields as private, so they can't be marshalled directly + type ExchangeRateJSON struct { + Hbars int32 `json:"hbars"` + Cents int32 `json:"cents"` + ExpirationTime string `json:"expirationTime"` + } + + if receipt.ExchangeRate != nil { + const layout = "2006-01-02T15:04:05.000Z" + expiration := time.Unix(receipt.ExchangeRate.expirationTime.Seconds, 0) + expirationStr := expiration.UTC().Format(layout) + + m["exchangeRate"] = ExchangeRateJSON{ + Hbars: receipt.ExchangeRate.Hbars, + Cents: receipt.ExchangeRate.cents, + ExpirationTime: expirationStr, + } + } + + // Handling fields with possible nil values + fields := map[string]interface{}{ + "topicId": receipt.TopicID, + "fileId": receipt.FileID, + "contractId": receipt.ContractID, + "accountId": receipt.AccountID, + "tokenId": receipt.TokenID, + "scheduleId": receipt.ScheduleID, + "scheduledTransactionId": receipt.ScheduledTransactionID, + } + for key, field := range fields { + m[key] = nil + if !reflect.ValueOf(field).IsNil() { + m[key] = fmt.Sprintf("%v", field) + } + } + + m["children"] = receipt.Children + m["duplicates"] = receipt.Duplicates + return m +} + +// MarshalJSON returns the JSON representation of the TransactionReceipt. +// This should yield the same result in all SDK's. +func (receipt TransactionReceipt) MarshalJSON() ([]byte, error) { + return json.Marshal(receipt._ToMap()) +} + func _TransactionReceiptFromProtobuf(protoResponse *services.TransactionGetReceiptResponse, transactionID *TransactionID) TransactionReceipt { if protoResponse == nil { return TransactionReceipt{} diff --git a/transaction_receipt_query_unit_test.go b/transaction_receipt_query_unit_test.go index 97bc04ffc..cf97bf8c8 100644 --- a/transaction_receipt_query_unit_test.go +++ b/transaction_receipt_query_unit_test.go @@ -24,6 +24,9 @@ package hedera */ import ( + "encoding/hex" + "encoding/json" + "fmt" "testing" "github.com/hashgraph/hedera-protobufs-go/services" @@ -208,3 +211,192 @@ func TestUnitTransactionReceiptUknown(t *testing.T) { require.NoError(t, err) require.Equal(t, StatusSuccess, receipt.Status) } + +func TestUnitTransactionReceiptToJson(t *testing.T) { + t.Parallel() + + responses := [][]interface{}{{ + &services.TransactionResponse{ + NodeTransactionPrecheckCode: services.ResponseCodeEnum_OK, + }, + &services.Response{ + Response: &services.Response_TransactionGetReceipt{ + TransactionGetReceipt: &services.TransactionGetReceiptResponse{ + Header: &services.ResponseHeader{ + Cost: 0, + ResponseType: services.ResponseType_ANSWER_ONLY, + }, + Receipt: &services.TransactionReceipt{ + Status: services.ResponseCodeEnum_SUCCESS, + AccountID: &services.AccountID{Account: &services.AccountID_AccountNum{ + AccountNum: 123, + }}, + ContractID: &services.ContractID{Contract: &services.ContractID_ContractNum{ + ContractNum: 456, + }}, + FileID: &services.FileID{FileNum: 789}, + TokenID: &services.TokenID{TokenNum: 987}, + SerialNumbers: []int64{1, 2, 3}, + TopicID: &services.TopicID{TopicNum: 654}, + ScheduleID: &services.ScheduleID{ScheduleNum: 321}, + ScheduledTransactionID: &services.TransactionID{ + AccountID: &services.AccountID{Account: &services.AccountID_AccountNum{ + AccountNum: 123, + }}, + TransactionValidStart: &services.Timestamp{ + Seconds: 1694689200, + }, + }, + TopicSequenceNumber: 10, + TopicRunningHash: []byte{10}, + ExchangeRate: &services.ExchangeRateSet{ + CurrentRate: &services.ExchangeRate{ + HbarEquiv: 30000, + CentEquiv: 154271, + ExpirationTime: &services.TimestampSeconds{ + Seconds: 1694689200, + }, + }, + }, + }, + ChildTransactionReceipts: []*services.TransactionReceipt{ + { + Status: services.ResponseCodeEnum_SUCCESS, + AccountID: &services.AccountID{Account: &services.AccountID_AccountNum{ + AccountNum: 123, + }}, + ContractID: &services.ContractID{Contract: &services.ContractID_ContractNum{ + ContractNum: 456, + }}, + FileID: &services.FileID{FileNum: 789}, + TokenID: &services.TokenID{TokenNum: 987}, + SerialNumbers: []int64{1, 2, 3}, + TopicID: &services.TopicID{TopicNum: 654}, + ScheduleID: &services.ScheduleID{ScheduleNum: 321}, + ScheduledTransactionID: &services.TransactionID{ + AccountID: &services.AccountID{Account: &services.AccountID_AccountNum{ + AccountNum: 123, + }}, + TransactionValidStart: &services.Timestamp{ + Seconds: 1694689200, + }, + }, + TopicSequenceNumber: 10, + TopicRunningHash: []byte{10}, + ExchangeRate: &services.ExchangeRateSet{ + CurrentRate: &services.ExchangeRate{ + HbarEquiv: 30000, + CentEquiv: 154271, + ExpirationTime: &services.TimestampSeconds{ + Seconds: 1694689200, + }, + }, + }, + }, + }, + DuplicateTransactionReceipts: []*services.TransactionReceipt{ + { + Status: services.ResponseCodeEnum_SUCCESS, + AccountID: &services.AccountID{Account: &services.AccountID_AccountNum{ + AccountNum: 123, + }}, + ContractID: &services.ContractID{Contract: &services.ContractID_ContractNum{ + ContractNum: 456, + }}, + FileID: &services.FileID{FileNum: 789}, + TokenID: &services.TokenID{TokenNum: 987}, + SerialNumbers: []int64{1, 2, 3}, + TopicID: &services.TopicID{TopicNum: 654}, + ScheduleID: &services.ScheduleID{ScheduleNum: 321}, + ScheduledTransactionID: &services.TransactionID{ + AccountID: &services.AccountID{Account: &services.AccountID_AccountNum{ + AccountNum: 123, + }}, + TransactionValidStart: &services.Timestamp{ + Seconds: 1694689200, + }, + }, + TopicSequenceNumber: 10, + TopicRunningHash: []byte{10}, + ExchangeRate: &services.ExchangeRateSet{ + CurrentRate: &services.ExchangeRate{ + HbarEquiv: 30000, + CentEquiv: 154271, + ExpirationTime: &services.TimestampSeconds{ + Seconds: 1694689200, + }, + }, + }, + }, + }, + }, + }, + }, + }} + client, server := NewMockClientAndServer(responses) + defer server.Close() + tx, err := NewTransferTransaction(). + SetNodeAccountIDs([]AccountID{{Account: 3}}). + AddHbarTransfer(AccountID{Account: 2}, HbarFromTinybar(-1)). + AddHbarTransfer(AccountID{Account: 3}, HbarFromTinybar(1)). + Execute(client) + require.NoError(t, err) + receipt, err := tx.SetValidateStatus(true).GetReceipt(client) + require.NoError(t, err) + jsonBytes, err := receipt.MarshalJSON() + fmt.Printf("receipt: %v\n", string(jsonBytes)) + require.NoError(t, err) + expected := `{"accountId":"0.0.123","children":[{"accountId":"0.0.123","children":[],"contractId":"0.0.456","duplicates":[],"exchangeRate": + {"hbars":30000,"cents":154271,"expirationTime":"2023-09-14T11:00:00.000Z"},"fileId":"0.0.789","scheduleId":"0.0.321", + "scheduledTransactionId":"0.0.123@1694689200.000000000","serialNumbers":[1,2,3],"status":"SUCCESS","tokenId":"0.0.987","topicId":"0.0.654", + "topicRunningHash":"0a","topicRunningHashVersion":0,"topicSequenceNumber":10,"totalSupply":0}],"contractId":"0.0.456", + "duplicates":[{"accountId":"0.0.123","children":[],"contractId":"0.0.456","duplicates":[],"exchangeRate":{"hbars":30000,"cents":154271, + "expirationTime":"2023-09-14T11:00:00.000Z"},"fileId":"0.0.789","scheduleId":"0.0.321","scheduledTransactionId":"0.0.123@1694689200.000000000", + "serialNumbers":[1,2,3],"status":"SUCCESS","tokenId":"0.0.987","topicId":"0.0.654","topicRunningHash":"0a","topicRunningHashVersion":0, + "topicSequenceNumber":10,"totalSupply":0}],"exchangeRate":{"hbars":30000,"cents":154271,"expirationTime":"2023-09-14T11:00:00.000Z"}, + "fileId":"0.0.789","scheduleId":"0.0.321","scheduledTransactionId":"0.0.123@1694689200.000000000","serialNumbers":[1,2,3],"status":"SUCCESS", + "tokenId":"0.0.987","topicId":"0.0.654","topicRunningHash":"0a","topicRunningHashVersion":0,"topicSequenceNumber":10,"totalSupply":0}` + + assert.JSONEqf(t, expected, string(jsonBytes), "json should be equal") + +} + +func TestUnitTransactionResponseToJson(t *testing.T) { + t.Parallel() + + responses := [][]interface{}{{ + &services.TransactionResponse{ + NodeTransactionPrecheckCode: services.ResponseCodeEnum_OK, + }, + &services.Response{ + Response: &services.Response_TransactionGetReceipt{ + TransactionGetReceipt: &services.TransactionGetReceiptResponse{ + Header: &services.ResponseHeader{ + Cost: 0, + ResponseType: services.ResponseType_ANSWER_ONLY, + }, + Receipt: &services.TransactionReceipt{ + Status: services.ResponseCodeEnum_SUCCESS, + }, + }, + }, + }, + }} + client, server := NewMockClientAndServer(responses) + defer server.Close() + tx, err := NewTransferTransaction(). + SetNodeAccountIDs([]AccountID{{Account: 3}}). + AddHbarTransfer(AccountID{Account: 2}, HbarFromTinybar(-1)). + AddHbarTransfer(AccountID{Account: 3}, HbarFromTinybar(1)). + Execute(client) + require.NoError(t, err) + jsonBytes, err := tx.MarshalJSON() + require.NoError(t, err) + obj := make(map[string]interface{}) + obj["nodeID"] = tx.NodeID.String() + obj["hash"] = hex.EncodeToString(tx.Hash) + obj["transactionID"] = tx.TransactionID.String() + expectedJSON, err := json.Marshal(obj) + require.NoError(t, err) + assert.Equal(t, expectedJSON, jsonBytes) +} diff --git a/transaction_record.go b/transaction_record.go index 6e37ff23d..0445a0d63 100644 --- a/transaction_record.go +++ b/transaction_record.go @@ -21,10 +21,12 @@ package hedera */ import ( + "encoding/hex" "fmt" - "time" - + jsoniter "github.com/json-iterator/go" "google.golang.org/protobuf/types/known/wrapperspb" + "sort" + "time" protobuf "google.golang.org/protobuf/proto" @@ -65,6 +67,162 @@ type TransactionRecord struct { EvmAddress []byte } +// MarshalJSON returns the JSON representation of the TransactionRecord +func (record TransactionRecord) MarshalJSON() ([]byte, error) { + var json = jsoniter.ConfigCompatibleWithStandardLibrary + m := make(map[string]interface{}) + type TransferJSON struct { + AccountID string `json:"accountId"` + Amount interface{} `json:"amount"` + IsApproved bool `json:"isApproved"` + } + var transfersJSON []TransferJSON + for _, t := range record.Transfers { + transfersJSON = append(transfersJSON, TransferJSON{ + AccountID: t.AccountID.String(), + Amount: fmt.Sprint(t.Amount.AsTinybar()), + IsApproved: t.IsApproved, + }) + } + + // It's werid because we need it without field names to match the JS SDK + tokenTransfersMap := make(map[string]map[string]string) + + for tokenId, transfers := range record.TokenTransfers { + accountIdMap := make(map[string]string) + for _, transfer := range transfers { + accountIdMap[transfer.AccountID.String()] = fmt.Sprint(transfer.Amount) + } + tokenTransfersMap[tokenId.String()] = accountIdMap + } + + type TransferNftTokensJSON struct { + SenderAccountID string `json:"sender"` + ReceiverAccountIDAccountID string `json:"recipient"` + IsApproved bool `json:"isApproved"` + SerialNumber int64 `json:"serial"` + } + var transfersNftTokenJSON []TransferNftTokensJSON + tokenTransfersNftMap := make(map[string][]TransferNftTokensJSON) + for k, v := range record.NftTransfers { + for _, tt := range v { + transfersNftTokenJSON = append(transfersNftTokenJSON, TransferNftTokensJSON{ + SenderAccountID: tt.SenderAccountID.String(), + ReceiverAccountIDAccountID: tt.ReceiverAccountID.String(), + IsApproved: tt.IsApproved, + SerialNumber: tt.SerialNumber, + }) + } + tokenTransfersNftMap[k.String()] = transfersNftTokenJSON + } + + m["transactionHash"] = hex.EncodeToString(record.TransactionHash) + m["consensusTimestamp"] = record.ConsensusTimestamp + m["transactionId"] = record.TransactionID.String() + m["transactionMemo"] = record.TransactionMemo + m["transactionFee"] = fmt.Sprint(record.TransactionFee.AsTinybar()) + m["transfers"] = transfersJSON + m["tokenTransfers"] = tokenTransfersMap + m["nftTransfers"] = tokenTransfersNftMap + m["expectedDecimals"] = record.ExpectedDecimals + m["callResultIsCreate"] = record.CallResultIsCreate + + type AssessedCustomFeeJSON struct { + FeeCollectorAccountID string `json:"feeCollectorAccountId"` + TokenID string `json:"tokenId"` + Amount string `json:"amount"` + PayerAccountIDs []string `json:"payerAccountIds"` + } + + customFeesJSON := make([]AssessedCustomFeeJSON, len(record.AssessedCustomFees)) + + for i, fee := range record.AssessedCustomFees { + payerAccountIDsStr := make([]string, len(fee.PayerAccountIDs)) + for j, accID := range fee.PayerAccountIDs { + payerAccountIDsStr[j] = accID.String() + } + customFeesJSON[i] = AssessedCustomFeeJSON{ + FeeCollectorAccountID: fee.FeeCollectorAccountId.String(), + TokenID: fee.TokenID.String(), + Amount: fmt.Sprint(fee.Amount), + PayerAccountIDs: payerAccountIDsStr, + } + } + + m["assessedCustomFees"] = customFeesJSON + + type TokenAssociationOutput struct { + TokenID string `json:"tokenId"` + AccountID string `json:"accountId"` + } + automaticTokenAssociations := make([]TokenAssociationOutput, len(record.AutomaticTokenAssociations)) + for i, ta := range record.AutomaticTokenAssociations { + automaticTokenAssociations[i] = TokenAssociationOutput{ + TokenID: ta.TokenID.String(), + AccountID: ta.AccountID.String(), + } + } + m["automaticTokenAssociations"] = automaticTokenAssociations + + formattedTime := record.ParentConsensusTimestamp.UTC().Format("2006-01-02T15:04:05.000Z") + m["parentConsensusTimestamp"] = formattedTime + + m["aliasKey"] = fmt.Sprint(record.AliasKey) + m["ethereumHash"] = hex.EncodeToString(record.EthereumHash) + type PaidStakingReward struct { + AccountID string `json:"accountId"` + Amount string `json:"amount"` + IsApproved bool `json:"isApproved"` + } + var paidStakingRewards []PaidStakingReward + for k, reward := range record.PaidStakingRewards { + paidStakingReward := PaidStakingReward{ + AccountID: k.String(), + Amount: fmt.Sprint(reward.AsTinybar()), + IsApproved: false, + } + paidStakingRewards = append(paidStakingRewards, paidStakingReward) + } + + sort.Slice(paidStakingRewards, func(i, j int) bool { + return paidStakingRewards[i].AccountID < paidStakingRewards[j].AccountID + }) + + m["paidStakingRewards"] = paidStakingRewards + + m["prngBytes"] = hex.EncodeToString(record.PrngBytes) + m["prngNumber"] = record.PrngNumber + m["evmAddress"] = hex.EncodeToString(record.EvmAddress) + + if record.Receipt.TransactionID != nil { + receiptJSON, err := record.Receipt.MarshalJSON() + if err != nil { + return nil, err + } + var receiptObj map[string]interface{} + if err := json.Unmarshal(receiptJSON, &receiptObj); err != nil { + return nil, err + } + m["receipt"] = receiptObj + } + m["children"] = record.Children + m["duplicates"] = record.Duplicates + + receiptBytes, err := record.Receipt.MarshalJSON() + if err != nil { + return nil, err + } + var receiptInterface interface{} + err = json.Unmarshal(receiptBytes, &receiptInterface) + if err != nil { + return nil, err + } + m["receipt"] = receiptInterface + + result, err := json.Marshal(m) + return result, err +} + // GetContractExecuteResult returns the ContractFunctionResult if the transaction was a contract call func (record TransactionRecord) GetContractExecuteResult() (ContractFunctionResult, error) { if record.CallResult == nil || record.CallResultIsCreate { diff --git a/transaction_record_query_unit_test.go b/transaction_record_query_unit_test.go index 072dc5456..73b2159d4 100644 --- a/transaction_record_query_unit_test.go +++ b/transaction_record_query_unit_test.go @@ -24,6 +24,7 @@ package hedera */ import ( + "encoding/hex" "fmt" "testing" "time" @@ -172,3 +173,90 @@ func TestUnitTransactionRecordReceiptNotFound(t *testing.T) { require.Equal(t, "exceptional precheck status RECEIPT_NOT_FOUND", err.Error()) require.Equal(t, StatusReceiptNotFound, record.Receipt.Status) } + +func TestUnitTransactionRecordQueryMarshalJSON(t *testing.T) { + t.Parallel() + hexRecord, err := hex.DecodeString(`1afe010a26081612070800100018de092a130a110801100c1a0b0880ae99a4ffffffffff013800420058001230cac44f2db045ba441f3fbc295217f2eb0f956293d28b3401578f6160e66f4e47ea87952d91c4b1cb5bda6447823b979a1a0c08f3fcb495061083d9be900322190a0c08e8fcb495061098f09cf20112070800100018850918002a0030bee8f013526c0a0f0a0608001000180510d0df820118000a0f0a0608001000186210f08dff1e18000a100a070800100018a00610def1ef0318000a100a070800100018a10610def1ef0318000a110a070800100018850910fbf8b7e10718000a110a070800100018de091080a8d6b90718008a0100aa0100`) + require.NoError(t, err) + record, err := TransactionRecordFromBytes([]byte(hexRecord)) + require.NoError(t, err) + accID, err := AccountIDFromString("0.0.1246") + require.NoError(t, err) + tokenID, err := TokenIDFromString("0.0.123") + require.NoError(t, err) + contractID, err := ContractIDFromString("0.0.3") + require.NoError(t, err) + record.Receipt.ContractID = &contractID + + tokenTransfer := TokenTransfer{ + AccountID: accID, + Amount: 789, + IsApproved: true, + } + tokenTransferList := map[TokenID][]TokenTransfer{} + tokenTransferList[tokenID] = []TokenTransfer{tokenTransfer} + + tokenNftTransfer := TokenNftTransfer{ + SenderAccountID: accID, + ReceiverAccountID: accID, + SerialNumber: 123, + IsApproved: true, + } + tokenNftTransferList := map[TokenID][]TokenNftTransfer{} + tokenNftTransferList[tokenID] = []TokenNftTransfer{tokenNftTransfer} + + assessedCustomFee := AssessedCustomFee{ + FeeCollectorAccountId: &accID, + Amount: 789, + TokenID: &tokenID, + PayerAccountIDs: []*AccountID{&accID}, + } + + tokenAssociation := TokenAssociation{ + AccountID: &accID, + TokenID: &tokenID, + } + + plaidStaking := map[AccountID]Hbar{} + for _, transfer := range record.Transfers { + plaidStaking[transfer.AccountID] = transfer.Amount + } + + pk, err := PublicKeyFromString("302a300506032b6570032100d7366c45e4d2f1a6c1d9af054f5ef8edc0b8d3875ba5d08a7f2e81ee8876e9e8") + require.NoError(t, err) + + prngNumber := int32(123) + evmAddressBytes, err := hex.DecodeString("deadbeef") + require.NoError(t, err) + + record.TransactionMemo = "test" + record.TokenTransfers = tokenTransferList + record.NftTransfers = tokenNftTransferList + record.ParentConsensusTimestamp = record.ConsensusTimestamp + record.AliasKey = &pk + record.EthereumHash = []byte{1, 2, 3, 4} + record.PaidStakingRewards = plaidStaking + record.PrngBytes = []byte{1, 2, 3, 4} + record.PrngNumber = &prngNumber + record.EvmAddress = evmAddressBytes + record.AssessedCustomFees = []AssessedCustomFee{assessedCustomFee} + record.AutomaticTokenAssociations = []TokenAssociation{tokenAssociation} + result, err := record.MarshalJSON() + require.NoError(t, err) + expected := `{"aliasKey":"302a300506032b6570032100d7366c45e4d2f1a6c1d9af054f5ef8edc0b8d3875ba5d08a7f2e81ee8876e9e8","assessedCustomFees": + [{"feeCollectorAccountId":"0.0.1246","tokenId":"0.0.123","amount":"789","payerAccountIds":["0.0.1246"]}],"automaticTokenAssociations": + [{"tokenId":"0.0.123","accountId":"0.0.1246"}],"callResultIsCreate":true,"children":[],"consensusTimestamp":"2022-06-18T05:54:43.839888003+03:00", + "duplicates":[],"ethereumHash":"01020304","evmAddress":"deadbeef","expectedDecimals":null,"nftTransfers":{"0.0.123":[{"sender":"0.0.1246","recipient": + "0.0.1246","isApproved":true,"serial":123}]},"paidStakingRewards":[{"accountId":"0.0.1157","amount":"-1041694270","isApproved":false},{"accountId": + "0.0.1246","amount":"1000000000","isApproved":false},{"accountId":"0.0.5","amount":"1071080","isApproved":false},{"accountId":"0.0.800","amount":"4062319", + "isApproved":false},{"accountId":"0.0.801","amount":"4062319","isApproved":false},{"accountId":"0.0.98","amount":"32498552","isApproved":false}], + "parentConsensusTimestamp":"2022-06-18T02:54:43.839Z","prngBytes":"01020304","prngNumber":123,"receipt":{"accountId":"0.0.1246","children":[], + "contractId":"0.0.3","duplicates":[],"exchangeRate":{"cents":12,"expirationTime":"1963-11-25T17:31:44.000Z","hbars":1},"fileId":null,"scheduleId": + null,"scheduledTransactionId":null,"serialNumbers":null,"status":"SUCCESS","tokenId":null,"topicId":null,"topicRunningHash":"","topicRunningHashVersion": + 0,"topicSequenceNumber":0,"totalSupply":0},"tokenTransfers":{"0.0.123":{"0.0.1246":"789"}},"transactionFee":"41694270","transactionHash": + "cac44f2db045ba441f3fbc295217f2eb0f956293d28b3401578f6160e66f4e47ea87952d91c4b1cb5bda6447823b979a","transactionId":"0.0.1157@1655520872.507983896", + "transactionMemo":"test","transfers":[{"accountId":"0.0.5","amount":"1071080","isApproved":false},{"accountId":"0.0.98","amount":"32498552", + "isApproved":false},{"accountId":"0.0.800","amount":"4062319","isApproved":false},{"accountId":"0.0.801","amount":"4062319","isApproved":false}, + {"accountId":"0.0.1157","amount":"-1041694270","isApproved":false},{"accountId":"0.0.1246","amount":"1000000000","isApproved":false}]}` + require.JSONEqf(t, expected, string(result), "json should be equal") +} diff --git a/transaction_response.go b/transaction_response.go index f61be65d6..9a3f01d5c 100644 --- a/transaction_response.go +++ b/transaction_response.go @@ -1,5 +1,10 @@ package hedera +import ( + "encoding/hex" + jsoniter "github.com/json-iterator/go" +) + /*- * * Hedera Go SDK @@ -33,6 +38,17 @@ type TransactionResponse struct { ValidateStatus bool } +// MarshalJSON returns the JSON representation of the TransactionResponse. +// This should yield the same result in all SDK's. +func (response TransactionResponse) MarshalJSON() ([]byte, error) { + var json = jsoniter.ConfigCompatibleWithStandardLibrary + obj := make(map[string]interface{}) + obj["nodeID"] = response.NodeID.String() + obj["hash"] = hex.EncodeToString(response.Hash) + obj["transactionID"] = response.TransactionID.String() + return json.Marshal(obj) +} + // GetReceipt retrieves the receipt for the transaction func (response TransactionResponse) GetReceipt(client *Client) (TransactionReceipt, error) { receipt, err := NewTransactionReceiptQuery(). diff --git a/transfer.go b/transfer.go index cd2c6b647..5a6dfe528 100644 --- a/transfer.go +++ b/transfer.go @@ -28,6 +28,7 @@ import ( type Transfer struct { AccountID AccountID Amount Hbar + IsApproved bool } func _TransferFromProtobuf(pb *services.AccountAmount) Transfer { @@ -43,7 +44,9 @@ func _TransferFromProtobuf(pb *services.AccountAmount) Transfer { return Transfer{ AccountID: accountID, Amount: HbarFromTinybar(pb.Amount), + IsApproved: pb.GetIsApproval(), } + } func (transfer Transfer) _ToProtobuf() *services.TransferList { // nolint