diff --git a/internal/transform/schema.go b/internal/transform/schema.go index 601ec5b3..f3f64aee 100644 --- a/internal/transform/schema.go +++ b/internal/transform/schema.go @@ -65,6 +65,9 @@ type TransactionOutput struct { SorobanResourcesReadBytes uint32 `json:"soroban_resources_read_bytes"` SorobanResourcesWriteBytes uint32 `json:"soroban_resources_write_bytes"` TransactionResultCode string `json:"transaction_result_code"` + InclusionFeeBid int64 `json:"inclusion_fee_bid"` + InclusionFeeCharged int64 `json:"inclusion_fee_charged"` + ResourceFeeRefund int64 `json:"resource_fee_refund"` } type LedgerTransactionOutput struct { diff --git a/internal/transform/transaction.go b/internal/transform/transaction.go index a5e6d79f..60a1a2b2 100644 --- a/internal/transform/transaction.go +++ b/internal/transform/transaction.go @@ -134,20 +134,43 @@ func TransformTransaction(transaction ingest.LedgerTransaction, lhe xdr.LedgerHe // Note: MaxFee and FeeCharged is the sum of base transaction fees + Soroban fees // Breakdown of Soroban fees can be calculated by the config_setting resource pricing * the resources used + var sorobanData xdr.SorobanTransactionData + var hasSorobanData bool var outputResourceFee int64 var outputSorobanResourcesInstructions uint32 var outputSorobanResourcesReadBytes uint32 var outputSorobanResourcesWriteBytes uint32 + var outputInclusionFeeBid int64 + var outputInclusionFeeCharged int64 + var outputResourceFeeRefund int64 + + // Soroban data can exist in V1 and FeeBump transactionEnvelopes + switch transaction.Envelope.Type { + case xdr.EnvelopeTypeEnvelopeTypeTx: + sorobanData, hasSorobanData = transaction.Envelope.V1.Tx.Ext.GetSorobanData() + case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: + sorobanData, hasSorobanData = transaction.Envelope.FeeBump.Tx.InnerTx.V1.Tx.Ext.GetSorobanData() + } + + if hasSorobanData { + outputResourceFee = int64(sorobanData.ResourceFee) + outputSorobanResourcesInstructions = uint32(sorobanData.Resources.Instructions) + outputSorobanResourcesReadBytes = uint32(sorobanData.Resources.ReadBytes) + outputSorobanResourcesWriteBytes = uint32(sorobanData.Resources.WriteBytes) + outputInclusionFeeBid = int64(transaction.Envelope.Fee()) - outputResourceFee + + accountBalanceStart, accountBalanceEnd := getAccountBalanceFromLedgerEntryChanges(transaction.FeeChanges, sourceAccount.Address()) + initialFeeCharged := accountBalanceStart - accountBalanceEnd + outputInclusionFeeCharged = initialFeeCharged - outputResourceFee - transactionEnvelopeV1, ok := transaction.Envelope.GetV1() - if ok { - sorobanData, ok := transactionEnvelopeV1.Tx.Ext.GetSorobanData() + meta, ok := transaction.UnsafeMeta.GetV3() if ok { - outputResourceFee = int64(sorobanData.ResourceFee) - outputSorobanResourcesInstructions = uint32(sorobanData.Resources.Instructions) - outputSorobanResourcesReadBytes = uint32(sorobanData.Resources.ReadBytes) - outputSorobanResourcesWriteBytes = uint32(sorobanData.Resources.WriteBytes) + accountBalanceStart, accountBalanceEnd := getAccountBalanceFromLedgerEntryChanges(meta.TxChangesAfter, sourceAccount.Address()) + outputResourceFeeRefund = accountBalanceEnd - accountBalanceStart } + + // TODO: FeeCharged is calculated incorrectly in protocol 20. Remove when protocol is updated and the bug is fixed + outputFeeCharged = outputFeeCharged - outputResourceFeeRefund } outputCloseTime, err := utils.TimePointToUTCTimeStamp(ledgerHeader.ScpValue.CloseTime) @@ -187,6 +210,9 @@ func TransformTransaction(transaction ingest.LedgerTransaction, lhe xdr.LedgerHe SorobanResourcesReadBytes: outputSorobanResourcesReadBytes, SorobanResourcesWriteBytes: outputSorobanResourcesWriteBytes, TransactionResultCode: outputTxResultCode, + InclusionFeeBid: outputInclusionFeeBid, + InclusionFeeCharged: outputInclusionFeeCharged, + ResourceFeeRefund: outputResourceFeeRefund, } // Add Muxed Account Details, if exists @@ -216,6 +242,36 @@ func TransformTransaction(transaction ingest.LedgerTransaction, lhe xdr.LedgerHe return transformedTransaction, nil } +func getAccountBalanceFromLedgerEntryChanges(changes xdr.LedgerEntryChanges, sourceAccountAddress string) (int64, int64) { + var accountBalanceStart int64 + var accountBalanceEnd int64 + + for _, change := range changes { + switch change.Type { + case xdr.LedgerEntryChangeTypeLedgerEntryUpdated: + accountEntry, ok := change.Updated.Data.GetAccount() + if !ok { + continue + } + + if accountEntry.AccountId.Address() == sourceAccountAddress { + accountBalanceEnd = int64(accountEntry.Balance) + } + case xdr.LedgerEntryChangeTypeLedgerEntryState: + accountEntry, ok := change.State.Data.GetAccount() + if !ok { + continue + } + + if accountEntry.AccountId.Address() == sourceAccountAddress { + accountBalanceStart = int64(accountEntry.Balance) + } + } + } + + return accountBalanceStart, accountBalanceEnd +} + func formatSigners(s []xdr.SignerKey) pq.StringArray { if s == nil { return nil