From 314b605e87aecae83f999a3b9daa54859265b6d1 Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Fri, 10 Jan 2025 12:59:28 -0700
Subject: [PATCH 01/12] xdrill for operations

---
 go.mod                          |    1 +
 go.sum                          |    2 +
 ingest/ledger_operation.go      | 1431 ++++++++++++++++++++++++++++++
 ingest/ledger_operation_test.go | 1474 +++++++++++++++++++++++++++++++
 ingest/ledger_transaction.go    |  160 ++++
 xdr/hash.go                     |   16 +-
 xdr/operation_trace_result.go   |   68 ++
 7 files changed, 3151 insertions(+), 1 deletion(-)
 create mode 100644 ingest/ledger_operation.go
 create mode 100644 ingest/ledger_operation_test.go
 create mode 100644 xdr/operation_trace_result.go

diff --git a/go.mod b/go.mod
index 341c85543e..6d7e5fab5d 100644
--- a/go.mod
+++ b/go.mod
@@ -58,6 +58,7 @@ require (
 
 require (
 	github.com/cenkalti/backoff/v4 v4.3.0
+	github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da
 	github.com/docker/docker v27.3.1+incompatible
 	github.com/docker/go-connections v0.5.0
 	github.com/fsouza/fake-gcs-server v1.49.2
diff --git a/go.sum b/go.sum
index a3c8ce08fa..b071601459 100644
--- a/go.sum
+++ b/go.sum
@@ -116,6 +116,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
 github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
 github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
 github.com/djherbis/fscache v0.10.1 h1:hDv+RGyvD+UDKyRYuLoVNbuRTnf2SrA2K3VyR1br9lk=
diff --git a/ingest/ledger_operation.go b/ingest/ledger_operation.go
new file mode 100644
index 0000000000..f59add11e2
--- /dev/null
+++ b/ingest/ledger_operation.go
@@ -0,0 +1,1431 @@
+package ingest
+
+import (
+	"encoding/base64"
+	"fmt"
+	"math/big"
+	"strconv"
+
+	"github.com/dgryski/go-farm"
+	"github.com/stellar/go/amount"
+	"github.com/stellar/go/support/contractevents"
+	"github.com/stellar/go/toid"
+	"github.com/stellar/go/xdr"
+)
+
+type LedgerOperation struct {
+	OperationIndex    int32
+	Operation         xdr.Operation
+	Transaction       *LedgerTransaction
+	NetworkPassphrase string
+}
+
+func (o *LedgerOperation) sourceAccountXDR() xdr.MuxedAccount {
+	sourceAccount := o.Operation.SourceAccount
+	if sourceAccount != nil {
+		return *sourceAccount
+	}
+
+	return o.Transaction.Envelope.SourceAccount()
+}
+
+func (o *LedgerOperation) SourceAccount() string {
+	muxedAccount := o.sourceAccountXDR()
+	providedID := muxedAccount.ToAccountId()
+	pointerToID := &providedID
+
+	return pointerToID.Address()
+}
+
+func (o *LedgerOperation) Type() int32 {
+	return int32(o.Operation.Body.Type)
+}
+
+func (o *LedgerOperation) TypeString() string {
+	return xdr.OperationTypeToStringMap[o.Type()]
+}
+
+func (o *LedgerOperation) ID() int64 {
+	//operationIndex needs +1 increment to stay in sync with ingest package
+	return toid.New(int32(o.Transaction.Ledger.LedgerSequence()), int32(o.Transaction.Index), o.OperationIndex+1).ToInt64()
+}
+
+func (o *LedgerOperation) SourceAccountMuxed() (string, bool) {
+	muxedAccount := o.sourceAccountXDR()
+	if muxedAccount.Type != xdr.CryptoKeyTypeKeyTypeMuxedEd25519 {
+		return "", false
+	}
+
+	return muxedAccount.Address(), true
+}
+
+func (o *LedgerOperation) OperationResultCode() string {
+	var operationResultCode string
+	operationResults, ok := o.Transaction.Result.Result.OperationResults()
+	if ok {
+		operationResultCode = operationResults[o.OperationIndex].Code.String()
+	}
+
+	return operationResultCode
+}
+
+func (o *LedgerOperation) OperationTraceCode() (string, error) {
+	var operationTraceCode string
+
+	operationResults, ok := o.Transaction.Result.Result.OperationResults()
+	if ok {
+		operationResultTr, ok := operationResults[o.OperationIndex].GetTr()
+		if ok {
+			operationTraceCode, err := operationResultTr.MapOperationResultTr()
+			if err != nil {
+				return "", err
+			}
+			return operationTraceCode, nil
+		}
+	}
+
+	return operationTraceCode, nil
+}
+
+func (o *LedgerOperation) OperationDetails() (map[string]interface{}, error) {
+	var err error
+	details := map[string]interface{}{}
+
+	switch o.Operation.Body.Type {
+	case xdr.OperationTypeCreateAccount:
+		details, err = o.CreateAccountDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypePayment:
+		details, err = o.PaymentDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypePathPaymentStrictReceive:
+		details, err = o.PathPaymentStrictReceiveDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypePathPaymentStrictSend:
+		details, err = o.PathPaymentStrictSendDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeManageBuyOffer:
+		details, err = o.ManageBuyOfferDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeManageSellOffer:
+		details, err = o.ManageSellOfferDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeCreatePassiveSellOffer:
+		details, err = o.CreatePassiveSellOfferDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeSetOptions:
+		details, err = o.SetOptionsDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeChangeTrust:
+		details, err = o.ChangeTrustDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeAllowTrust:
+		details, err = o.AllowTrustDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeAccountMerge:
+		details, err = o.AccountMergeDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeInflation:
+		details, err = o.InflationDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeManageData:
+		details, err = o.ManageDataDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeBumpSequence:
+		details, err = o.BumpSequenceDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeCreateClaimableBalance:
+		details, err = o.CreateClaimableBalanceDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeClaimClaimableBalance:
+		details, err = o.ClaimClaimableBalanceDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeBeginSponsoringFutureReserves:
+		details, err = o.BeginSponsoringFutureReservesDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeEndSponsoringFutureReserves:
+		details, err = o.EndSponsoringFutureReserveDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeRevokeSponsorship:
+		details, err = o.RevokeSponsorshipDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeClawback:
+		details, err = o.ClawbackDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeClawbackClaimableBalance:
+		details, err = o.ClawbackClaimableBalanceDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeSetTrustLineFlags:
+		details, err = o.SetTrustLineFlagsDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeLiquidityPoolDeposit:
+		details, err = o.LiquidityPoolDepositDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeLiquidityPoolWithdraw:
+		details, err = o.LiquidityPoolWithdrawDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeInvokeHostFunction:
+		details, err = o.InvokeHostFunctionDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeExtendFootprintTtl:
+		details, err = o.ExtendFootprintTtlDetails()
+		if err != nil {
+			return details, err
+		}
+	case xdr.OperationTypeRestoreFootprint:
+		details, err = o.RestoreFootprintDetails()
+		if err != nil {
+			return details, err
+		}
+	default:
+		return details, fmt.Errorf("unknown operation type: %s", o.Operation.Body.Type.String())
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) CreateAccountDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetCreateAccountOp()
+	if !ok {
+		return details, fmt.Errorf("could not access CreateAccount info for this operation (index %d)", o.OperationIndex)
+	}
+
+	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "funder"); err != nil {
+		return details, err
+	}
+	details["account"] = op.Destination.Address()
+	details["starting_balance"] = o.ConvertStroopValueToReal(op.StartingBalance)
+
+	return details, nil
+}
+
+func (o *LedgerOperation) addAccountAndMuxedAccountDetails(result map[string]interface{}, a xdr.MuxedAccount, prefix string) error {
+	account_id := a.ToAccountId()
+	result[prefix] = account_id.Address()
+	prefix = o.FormatPrefix(prefix)
+	if a.Type == xdr.CryptoKeyTypeKeyTypeMuxedEd25519 {
+		muxedAccountAddress, err := a.GetAddress()
+		if err != nil {
+			return err
+		}
+		result[prefix+"muxed"] = muxedAccountAddress
+		muxedAccountId, err := a.GetId()
+		if err != nil {
+			return err
+		}
+		result[prefix+"muxed_id"] = muxedAccountId
+	}
+	return nil
+}
+
+func (o *LedgerOperation) PaymentDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetPaymentOp()
+	if !ok {
+		return details, fmt.Errorf("could not access Payment info for this operation (index %d)", o.OperationIndex)
+	}
+
+	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "from"); err != nil {
+		return details, err
+	}
+	if err := o.addAccountAndMuxedAccountDetails(details, op.Destination, "to"); err != nil {
+		return details, err
+	}
+	details["amount"] = o.ConvertStroopValueToReal(op.Amount)
+	if err := o.addAssetDetailsToOperationDetails(details, op.Asset, ""); err != nil {
+		return details, err
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) addAssetDetailsToOperationDetails(result map[string]interface{}, asset xdr.Asset, prefix string) error {
+	var assetType, code, issuer string
+	err := asset.Extract(&assetType, &code, &issuer)
+	if err != nil {
+		return err
+	}
+
+	prefix = o.FormatPrefix(prefix)
+	result[prefix+"asset_type"] = assetType
+
+	if asset.Type == xdr.AssetTypeAssetTypeNative {
+		result[prefix+"asset_id"] = int64(-5706705804583548011)
+		return nil
+	}
+
+	result[prefix+"asset_code"] = code
+	result[prefix+"asset_issuer"] = issuer
+	result[prefix+"asset_id"] = o.FarmHashAsset(code, issuer, assetType)
+
+	return nil
+}
+
+func (o *LedgerOperation) PathPaymentStrictReceiveDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetPathPaymentStrictReceiveOp()
+	if !ok {
+		return details, fmt.Errorf("could not access PathPaymentStrictReceive info for this operation (index %d)", o.OperationIndex)
+	}
+
+	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "from"); err != nil {
+		return details, err
+	}
+	if err := o.addAccountAndMuxedAccountDetails(details, op.Destination, "to"); err != nil {
+		return details, err
+	}
+	details["amount"] = o.ConvertStroopValueToReal(op.DestAmount)
+	details["source_amount"] = amount.String(0)
+	details["source_max"] = o.ConvertStroopValueToReal(op.SendMax)
+	if err := o.addAssetDetailsToOperationDetails(details, op.DestAsset, ""); err != nil {
+		return details, err
+	}
+	if err := o.addAssetDetailsToOperationDetails(details, op.SendAsset, "source"); err != nil {
+		return details, err
+	}
+
+	if o.Transaction.Successful() {
+		allOperationResults, ok := o.Transaction.Result.OperationResults()
+		if !ok {
+			return details, fmt.Errorf("could not access any results for this transaction")
+		}
+		currentOperationResult := allOperationResults[o.OperationIndex]
+		resultBody, ok := currentOperationResult.GetTr()
+		if !ok {
+			return details, fmt.Errorf("could not access result body for this operation (index %d)", o.OperationIndex)
+		}
+		result, ok := resultBody.GetPathPaymentStrictReceiveResult()
+		if !ok {
+			return details, fmt.Errorf("could not access PathPaymentStrictReceive result info for this operation (index %d)", o.OperationIndex)
+		}
+		details["source_amount"] = o.ConvertStroopValueToReal(result.SendAmount())
+	}
+
+	details["path"] = o.TransformPath(op.Path)
+	return details, nil
+}
+
+func (o *LedgerOperation) PathPaymentStrictSendDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetPathPaymentStrictSendOp()
+	if !ok {
+		return details, fmt.Errorf("could not access PathPaymentStrictSend info for this operation (index %d)", o.OperationIndex)
+	}
+
+	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "from"); err != nil {
+		return details, err
+	}
+	if err := o.addAccountAndMuxedAccountDetails(details, op.Destination, "to"); err != nil {
+		return details, err
+	}
+	details["amount"] = float64(0)
+	details["source_amount"] = o.ConvertStroopValueToReal(op.SendAmount)
+	details["destination_min"] = amount.String(op.DestMin)
+	if err := o.addAssetDetailsToOperationDetails(details, op.DestAsset, ""); err != nil {
+		return details, err
+	}
+	if err := o.addAssetDetailsToOperationDetails(details, op.SendAsset, "source"); err != nil {
+		return details, err
+	}
+
+	if o.Transaction.Successful() {
+		allOperationResults, ok := o.Transaction.Result.OperationResults()
+		if !ok {
+			return details, fmt.Errorf("could not access any results for this transaction")
+		}
+		currentOperationResult := allOperationResults[o.OperationIndex]
+		resultBody, ok := currentOperationResult.GetTr()
+		if !ok {
+			return details, fmt.Errorf("could not access result body for this operation (index %d)", o.OperationIndex)
+		}
+		result, ok := resultBody.GetPathPaymentStrictSendResult()
+		if !ok {
+			return details, fmt.Errorf("could not access GetPathPaymentStrictSendResult result info for this operation (index %d)", o.OperationIndex)
+		}
+		details["amount"] = o.ConvertStroopValueToReal(result.DestAmount())
+	}
+
+	details["path"] = o.TransformPath(op.Path)
+
+	return details, nil
+}
+func (o *LedgerOperation) ManageBuyOfferDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetManageBuyOfferOp()
+	if !ok {
+		return details, fmt.Errorf("could not access ManageBuyOffer info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["offer_id"] = int64(op.OfferId)
+	details["amount"] = o.ConvertStroopValueToReal(op.BuyAmount)
+	if err := o.addPriceDetails(details, op.Price, ""); err != nil {
+		return details, err
+	}
+
+	if err := o.addAssetDetailsToOperationDetails(details, op.Buying, "buying"); err != nil {
+		return details, err
+	}
+	if err := o.addAssetDetailsToOperationDetails(details, op.Selling, "selling"); err != nil {
+		return details, err
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) addPriceDetails(result map[string]interface{}, price xdr.Price, prefix string) error {
+	prefix = o.FormatPrefix(prefix)
+	parsedPrice, err := strconv.ParseFloat(price.String(), 64)
+	if err != nil {
+		return err
+	}
+	result[prefix+"price"] = parsedPrice
+	result[prefix+"price_r"] = Price{
+		Numerator:   int32(price.N),
+		Denominator: int32(price.D),
+	}
+	return nil
+}
+
+func (o *LedgerOperation) ManageSellOfferDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetManageSellOfferOp()
+	if !ok {
+		return details, fmt.Errorf("could not access ManageSellOffer info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["offer_id"] = int64(op.OfferId)
+	details["amount"] = o.ConvertStroopValueToReal(op.Amount)
+	if err := o.addPriceDetails(details, op.Price, ""); err != nil {
+		return details, err
+	}
+
+	if err := o.addAssetDetailsToOperationDetails(details, op.Buying, "buying"); err != nil {
+		return details, err
+	}
+	if err := o.addAssetDetailsToOperationDetails(details, op.Selling, "selling"); err != nil {
+		return details, err
+	}
+
+	return details, nil
+}
+func (o *LedgerOperation) CreatePassiveSellOfferDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetCreatePassiveSellOfferOp()
+	if !ok {
+		return details, fmt.Errorf("could not access CreatePassiveSellOffer info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["amount"] = o.ConvertStroopValueToReal(op.Amount)
+	if err := o.addPriceDetails(details, op.Price, ""); err != nil {
+		return details, err
+	}
+
+	if err := o.addAssetDetailsToOperationDetails(details, op.Buying, "buying"); err != nil {
+		return details, err
+	}
+	if err := o.addAssetDetailsToOperationDetails(details, op.Selling, "selling"); err != nil {
+		return details, err
+	}
+
+	return details, nil
+}
+func (o *LedgerOperation) SetOptionsDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetSetOptionsOp()
+	if !ok {
+		return details, fmt.Errorf("could not access GetSetOptions info for this operation (index %d)", o.OperationIndex)
+	}
+
+	if op.InflationDest != nil {
+		details["inflation_dest"] = op.InflationDest.Address()
+	}
+
+	if op.SetFlags != nil && *op.SetFlags > 0 {
+		o.addOperationFlagToOperationDetails(details, uint32(*op.SetFlags), "set")
+	}
+
+	if op.ClearFlags != nil && *op.ClearFlags > 0 {
+		o.addOperationFlagToOperationDetails(details, uint32(*op.ClearFlags), "clear")
+	}
+
+	if op.MasterWeight != nil {
+		details["master_key_weight"] = uint32(*op.MasterWeight)
+	}
+
+	if op.LowThreshold != nil {
+		details["low_threshold"] = uint32(*op.LowThreshold)
+	}
+
+	if op.MedThreshold != nil {
+		details["med_threshold"] = uint32(*op.MedThreshold)
+	}
+
+	if op.HighThreshold != nil {
+		details["high_threshold"] = uint32(*op.HighThreshold)
+	}
+
+	if op.HomeDomain != nil {
+		details["home_domain"] = string(*op.HomeDomain)
+	}
+
+	if op.Signer != nil {
+		details["signer_key"] = op.Signer.Key.Address()
+		details["signer_weight"] = uint32(op.Signer.Weight)
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) addOperationFlagToOperationDetails(result map[string]interface{}, flag uint32, prefix string) {
+	intFlags := make([]int32, 0)
+	stringFlags := make([]string, 0)
+
+	if (int64(flag) & int64(xdr.AccountFlagsAuthRequiredFlag)) > 0 {
+		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthRequiredFlag))
+		stringFlags = append(stringFlags, "auth_required")
+	}
+
+	if (int64(flag) & int64(xdr.AccountFlagsAuthRevocableFlag)) > 0 {
+		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthRevocableFlag))
+		stringFlags = append(stringFlags, "auth_revocable")
+	}
+
+	if (int64(flag) & int64(xdr.AccountFlagsAuthImmutableFlag)) > 0 {
+		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthImmutableFlag))
+		stringFlags = append(stringFlags, "auth_immutable")
+	}
+
+	if (int64(flag) & int64(xdr.AccountFlagsAuthClawbackEnabledFlag)) > 0 {
+		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthClawbackEnabledFlag))
+		stringFlags = append(stringFlags, "auth_clawback_enabled")
+	}
+
+	prefix = o.FormatPrefix(prefix)
+	result[prefix+"flags"] = intFlags
+	result[prefix+"flags_s"] = stringFlags
+}
+
+func (o *LedgerOperation) ChangeTrustDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetChangeTrustOp()
+	if !ok {
+		return details, fmt.Errorf("could not access GetChangeTrust info for this operation (index %d)", o.OperationIndex)
+	}
+
+	if op.Line.Type == xdr.AssetTypeAssetTypePoolShare {
+		if err := o.addLiquidityPoolAssetDetails(details, *op.Line.LiquidityPool); err != nil {
+			return details, err
+		}
+	} else {
+		if err := o.addAssetDetailsToOperationDetails(details, op.Line.ToAsset(), ""); err != nil {
+			return details, err
+		}
+		details["trustee"] = details["asset_issuer"]
+	}
+
+	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "trustor"); err != nil {
+		return details, err
+	}
+
+	details["limit"] = o.ConvertStroopValueToReal(op.Limit)
+
+	return details, nil
+}
+
+func (o *LedgerOperation) addLiquidityPoolAssetDetails(result map[string]interface{}, lpp xdr.LiquidityPoolParameters) error {
+	result["asset_type"] = "liquidity_pool_shares"
+	if lpp.Type != xdr.LiquidityPoolTypeLiquidityPoolConstantProduct {
+		return fmt.Errorf("unknown liquidity pool type %d", lpp.Type)
+	}
+	cp := lpp.ConstantProduct
+	poolID, err := xdr.NewPoolId(cp.AssetA, cp.AssetB, cp.Fee)
+	if err != nil {
+		return err
+	}
+
+	result["liquidity_pool_id"] = o.PoolIDToString(poolID)
+
+	return nil
+}
+
+func (o *LedgerOperation) AllowTrustDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetAllowTrustOp()
+	if !ok {
+		return details, fmt.Errorf("could not access AllowTrust info for this operation (index %d)", o.OperationIndex)
+	}
+
+	if err := o.addAssetDetailsToOperationDetails(details, op.Asset.ToAsset(o.sourceAccountXDR().ToAccountId()), ""); err != nil {
+		return details, err
+	}
+	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "trustee"); err != nil {
+		return details, err
+	}
+	details["trustor"] = op.Trustor.Address()
+	shouldAuth := xdr.TrustLineFlags(op.Authorize).IsAuthorized()
+	details["authorize"] = shouldAuth
+	shouldAuthLiabilities := xdr.TrustLineFlags(op.Authorize).IsAuthorizedToMaintainLiabilitiesFlag()
+	if shouldAuthLiabilities {
+		details["authorize_to_maintain_liabilities"] = shouldAuthLiabilities
+	}
+	shouldClawbackEnabled := xdr.TrustLineFlags(op.Authorize).IsClawbackEnabledFlag()
+	if shouldClawbackEnabled {
+		details["clawback_enabled"] = shouldClawbackEnabled
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) AccountMergeDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	destinationAccount, ok := o.Operation.Body.GetDestination()
+	if !ok {
+		return details, fmt.Errorf("could not access Destination info for this operation (index %d)", o.OperationIndex)
+	}
+
+	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "account"); err != nil {
+		return details, err
+	}
+	if err := o.addAccountAndMuxedAccountDetails(details, destinationAccount, "into"); err != nil {
+		return details, err
+	}
+
+	return details, nil
+}
+
+// Inflation operations don't have information that affects the details struct
+func (o *LedgerOperation) InflationDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	return details, nil
+}
+
+func (o *LedgerOperation) ManageDataDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetManageDataOp()
+	if !ok {
+		return details, fmt.Errorf("could not access GetManageData info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["name"] = string(op.DataName)
+	if op.DataValue != nil {
+		details["value"] = base64.StdEncoding.EncodeToString(*op.DataValue)
+	} else {
+		details["value"] = nil
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) BumpSequenceDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetBumpSequenceOp()
+	if !ok {
+		return details, fmt.Errorf("could not access BumpSequence info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["bump_to"] = fmt.Sprintf("%d", op.BumpTo)
+
+	return details, nil
+}
+func (o *LedgerOperation) CreateClaimableBalanceDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetCreateClaimableBalanceOp()
+	if !ok {
+		return details, fmt.Errorf("could not access CreateClaimableBalance info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["asset"] = op.Asset.StringCanonical()
+	details["amount"] = o.ConvertStroopValueToReal(op.Amount)
+	details["claimants"] = o.TransformClaimants(op.Claimants)
+
+	return details, nil
+}
+
+func (o *LedgerOperation) ClaimClaimableBalanceDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetClaimClaimableBalanceOp()
+	if !ok {
+		return details, fmt.Errorf("could not access ClaimClaimableBalance info for this operation (index %d)", o.OperationIndex)
+	}
+
+	balanceID, err := xdr.MarshalHex(op.BalanceId)
+	if err != nil {
+		return details, fmt.Errorf("invalid balanceId in op: %d", o.OperationIndex)
+	}
+	details["balance_id"] = balanceID
+	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "claimant"); err != nil {
+		return details, err
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) BeginSponsoringFutureReservesDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetBeginSponsoringFutureReservesOp()
+	if !ok {
+		return details, fmt.Errorf("could not access BeginSponsoringFutureReserves info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["sponsored_id"] = op.SponsoredId.Address()
+
+	return details, nil
+}
+
+func (o *LedgerOperation) EndSponsoringFutureReserveDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	beginSponsorOp := o.findInitatingBeginSponsoringOp()
+	if beginSponsorOp != nil {
+		beginSponsorshipSource := o.sourceAccountXDR()
+		if err := o.addAccountAndMuxedAccountDetails(details, beginSponsorshipSource, "begin_sponsor"); err != nil {
+			return details, err
+		}
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) findInitatingBeginSponsoringOp() *SponsorshipOutput {
+	if !o.Transaction.Successful() {
+		// Failed transactions may not have a compliant sandwich structure
+		// we can rely on (e.g. invalid nesting or a being operation with the wrong sponsoree ID)
+		// and thus we bail out since we could return incorrect information.
+		return nil
+	}
+	sponsoree := o.sourceAccountXDR().ToAccountId()
+	operations := o.Transaction.Envelope.Operations()
+	for i := int(o.OperationIndex) - 1; i >= 0; i-- {
+		if beginOp, ok := operations[i].Body.GetBeginSponsoringFutureReservesOp(); ok &&
+			beginOp.SponsoredId.Address() == sponsoree.Address() {
+			result := SponsorshipOutput{
+				Operation:      operations[i],
+				OperationIndex: uint32(i),
+			}
+			return &result
+		}
+	}
+	return nil
+}
+
+func (o *LedgerOperation) RevokeSponsorshipDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetRevokeSponsorshipOp()
+	if !ok {
+		return details, fmt.Errorf("could not access RevokeSponsorship info for this operation (index %d)", o.OperationIndex)
+	}
+
+	switch op.Type {
+	case xdr.RevokeSponsorshipTypeRevokeSponsorshipLedgerEntry:
+		if err := o.addLedgerKeyToDetails(details, *op.LedgerKey); err != nil {
+			return details, err
+		}
+	case xdr.RevokeSponsorshipTypeRevokeSponsorshipSigner:
+		details["signer_account_id"] = op.Signer.AccountId.Address()
+		details["signer_key"] = op.Signer.SignerKey.Address()
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) addLedgerKeyToDetails(result map[string]interface{}, ledgerKey xdr.LedgerKey) error {
+	switch ledgerKey.Type {
+	case xdr.LedgerEntryTypeAccount:
+		result["account_id"] = ledgerKey.Account.AccountId.Address()
+	case xdr.LedgerEntryTypeClaimableBalance:
+		marshalHex, err := xdr.MarshalHex(ledgerKey.ClaimableBalance.BalanceId)
+		if err != nil {
+			return fmt.Errorf("in claimable balance: %w", err)
+		}
+		result["claimable_balance_id"] = marshalHex
+	case xdr.LedgerEntryTypeData:
+		result["data_account_id"] = ledgerKey.Data.AccountId.Address()
+		result["data_name"] = string(ledgerKey.Data.DataName)
+	case xdr.LedgerEntryTypeOffer:
+		result["offer_id"] = int64(ledgerKey.Offer.OfferId)
+	case xdr.LedgerEntryTypeTrustline:
+		result["trustline_account_id"] = ledgerKey.TrustLine.AccountId.Address()
+		if ledgerKey.TrustLine.Asset.Type == xdr.AssetTypeAssetTypePoolShare {
+			result["trustline_liquidity_pool_id"] = o.PoolIDToString(*ledgerKey.TrustLine.Asset.LiquidityPoolId)
+		} else {
+			result["trustline_asset"] = ledgerKey.TrustLine.Asset.ToAsset().StringCanonical()
+		}
+	case xdr.LedgerEntryTypeLiquidityPool:
+		result["liquidity_pool_id"] = o.PoolIDToString(ledgerKey.LiquidityPool.LiquidityPoolId)
+	}
+	return nil
+}
+
+func (o *LedgerOperation) ClawbackDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetClawbackOp()
+	if !ok {
+		return details, fmt.Errorf("could not access Clawback info for this operation (index %d)", o.OperationIndex)
+	}
+
+	if err := o.addAssetDetailsToOperationDetails(details, op.Asset, ""); err != nil {
+		return details, err
+	}
+	if err := o.addAccountAndMuxedAccountDetails(details, op.From, "from"); err != nil {
+		return details, err
+	}
+	details["amount"] = o.ConvertStroopValueToReal(op.Amount)
+
+	return details, nil
+}
+func (o *LedgerOperation) ClawbackClaimableBalanceDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetClawbackClaimableBalanceOp()
+	if !ok {
+		return details, fmt.Errorf("could not access ClawbackClaimableBalance info for this operation (index %d)", o.OperationIndex)
+	}
+
+	balanceID, err := xdr.MarshalHex(op.BalanceId)
+	if err != nil {
+		return details, fmt.Errorf("invalid balanceId in op: %d", o.OperationIndex)
+	}
+	details["balance_id"] = balanceID
+
+	return details, nil
+}
+func (o *LedgerOperation) SetTrustLineFlagsDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetSetTrustLineFlagsOp()
+	if !ok {
+		return details, fmt.Errorf("could not access SetTrustLineFlags info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["trustor"] = op.Trustor.Address()
+	if err := o.addAssetDetailsToOperationDetails(details, op.Asset, ""); err != nil {
+		return details, err
+	}
+	if op.SetFlags > 0 {
+		o.addTrustLineFlagToDetails(details, xdr.TrustLineFlags(op.SetFlags), "set")
+
+	}
+	if op.ClearFlags > 0 {
+		o.addTrustLineFlagToDetails(details, xdr.TrustLineFlags(op.ClearFlags), "clear")
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) addTrustLineFlagToDetails(result map[string]interface{}, f xdr.TrustLineFlags, prefix string) {
+	var (
+		n []int32
+		s []string
+	)
+
+	if f.IsAuthorized() {
+		n = append(n, int32(xdr.TrustLineFlagsAuthorizedFlag))
+		s = append(s, "authorized")
+	}
+
+	if f.IsAuthorizedToMaintainLiabilitiesFlag() {
+		n = append(n, int32(xdr.TrustLineFlagsAuthorizedToMaintainLiabilitiesFlag))
+		s = append(s, "authorized_to_maintain_liabilities")
+	}
+
+	if f.IsClawbackEnabledFlag() {
+		n = append(n, int32(xdr.TrustLineFlagsTrustlineClawbackEnabledFlag))
+		s = append(s, "clawback_enabled")
+	}
+
+	prefix = o.FormatPrefix(prefix)
+	result[prefix+"flags"] = n
+	result[prefix+"flags_s"] = s
+}
+
+func (o *LedgerOperation) LiquidityPoolDepositDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetLiquidityPoolDepositOp()
+	if !ok {
+		return details, fmt.Errorf("could not access LiquidityPoolDeposit info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["liquidity_pool_id"] = o.PoolIDToString(op.LiquidityPoolId)
+	var (
+		assetA, assetB         xdr.Asset
+		depositedA, depositedB xdr.Int64
+		sharesReceived         xdr.Int64
+	)
+	if o.Transaction.Successful() {
+		// we will use the defaults (omitted asset and 0 amounts) if the transaction failed
+		lp, delta, err := o.getLiquidityPoolAndProductDelta(&op.LiquidityPoolId)
+		if err != nil {
+			return nil, err
+		}
+		params := lp.Body.ConstantProduct.Params
+		assetA, assetB = params.AssetA, params.AssetB
+		depositedA, depositedB = delta.ReserveA, delta.ReserveB
+		sharesReceived = delta.TotalPoolShares
+	}
+
+	// Process ReserveA Details
+	if err := o.addAssetDetailsToOperationDetails(details, assetA, "reserve_a"); err != nil {
+		return details, err
+	}
+	details["reserve_a_max_amount"] = o.ConvertStroopValueToReal(op.MaxAmountA)
+	depositA, err := strconv.ParseFloat(amount.String(depositedA), 64)
+	if err != nil {
+		return details, err
+	}
+	details["reserve_a_deposit_amount"] = depositA
+
+	//Process ReserveB Details
+	if err := o.addAssetDetailsToOperationDetails(details, assetB, "reserve_b"); err != nil {
+		return details, err
+	}
+	details["reserve_b_max_amount"] = o.ConvertStroopValueToReal(op.MaxAmountB)
+	depositB, err := strconv.ParseFloat(amount.String(depositedB), 64)
+	if err != nil {
+		return details, err
+	}
+	details["reserve_b_deposit_amount"] = depositB
+
+	if err := o.addPriceDetails(details, op.MinPrice, "min"); err != nil {
+		return details, err
+	}
+	if err := o.addPriceDetails(details, op.MaxPrice, "max"); err != nil {
+		return details, err
+	}
+
+	sharesToFloat, err := strconv.ParseFloat(amount.String(sharesReceived), 64)
+	if err != nil {
+		return details, err
+	}
+	details["shares_received"] = sharesToFloat
+
+	return details, nil
+}
+
+// operation xdr.Operation, operationIndex int32, transaction LedgerTransaction, ledgerSeq int32
+func (o *LedgerOperation) getLiquidityPoolAndProductDelta(lpID *xdr.PoolId) (*xdr.LiquidityPoolEntry, *LiquidityPoolDelta, error) {
+	changes, err := o.Transaction.GetOperationChanges(uint32(o.OperationIndex))
+	if err != nil {
+		return nil, nil, err
+	}
+
+	for _, c := range changes {
+		if c.Type != xdr.LedgerEntryTypeLiquidityPool {
+			continue
+		}
+		// The delta can be caused by a full removal or full creation of the liquidity pool
+		var lp *xdr.LiquidityPoolEntry
+		var preA, preB, preShares xdr.Int64
+		if c.Pre != nil {
+			if lpID != nil && c.Pre.Data.LiquidityPool.LiquidityPoolId != *lpID {
+				// if we were looking for specific pool id, then check on it
+				continue
+			}
+			lp = c.Pre.Data.LiquidityPool
+			if c.Pre.Data.LiquidityPool.Body.Type != xdr.LiquidityPoolTypeLiquidityPoolConstantProduct {
+				return nil, nil, fmt.Errorf("unexpected liquity pool body type %d", c.Pre.Data.LiquidityPool.Body.Type)
+			}
+			cpPre := c.Pre.Data.LiquidityPool.Body.ConstantProduct
+			preA, preB, preShares = cpPre.ReserveA, cpPre.ReserveB, cpPre.TotalPoolShares
+		}
+		var postA, postB, postShares xdr.Int64
+		if c.Post != nil {
+			if lpID != nil && c.Post.Data.LiquidityPool.LiquidityPoolId != *lpID {
+				// if we were looking for specific pool id, then check on it
+				continue
+			}
+			lp = c.Post.Data.LiquidityPool
+			if c.Post.Data.LiquidityPool.Body.Type != xdr.LiquidityPoolTypeLiquidityPoolConstantProduct {
+				return nil, nil, fmt.Errorf("unexpected liquity pool body type %d", c.Post.Data.LiquidityPool.Body.Type)
+			}
+			cpPost := c.Post.Data.LiquidityPool.Body.ConstantProduct
+			postA, postB, postShares = cpPost.ReserveA, cpPost.ReserveB, cpPost.TotalPoolShares
+		}
+		delta := &LiquidityPoolDelta{
+			ReserveA:        postA - preA,
+			ReserveB:        postB - preB,
+			TotalPoolShares: postShares - preShares,
+		}
+		return lp, delta, nil
+	}
+
+	return nil, nil, fmt.Errorf("liquidity pool change not found")
+}
+
+func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetLiquidityPoolWithdrawOp()
+	if !ok {
+		return details, fmt.Errorf("could not access LiquidityPoolWithdraw info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["liquidity_pool_id"] = o.PoolIDToString(op.LiquidityPoolId)
+	var (
+		assetA, assetB       xdr.Asset
+		receivedA, receivedB xdr.Int64
+	)
+	if o.Transaction.Successful() {
+		// we will use the defaults (omitted asset and 0 amounts) if the transaction failed
+		lp, delta, err := o.getLiquidityPoolAndProductDelta(&op.LiquidityPoolId)
+		if err != nil {
+			return nil, err
+		}
+		params := lp.Body.ConstantProduct.Params
+		assetA, assetB = params.AssetA, params.AssetB
+		receivedA, receivedB = -delta.ReserveA, -delta.ReserveB
+	}
+	// Process AssetA Details
+	if err := o.addAssetDetailsToOperationDetails(details, assetA, "reserve_a"); err != nil {
+		return details, err
+	}
+	details["reserve_a_min_amount"] = o.ConvertStroopValueToReal(op.MinAmountA)
+	details["reserve_a_withdraw_amount"] = o.ConvertStroopValueToReal(receivedA)
+
+	// Process AssetB Details
+	if err := o.addAssetDetailsToOperationDetails(details, assetB, "reserve_b"); err != nil {
+		return details, err
+	}
+	details["reserve_b_min_amount"] = o.ConvertStroopValueToReal(op.MinAmountB)
+	details["reserve_b_withdraw_amount"] = o.ConvertStroopValueToReal(receivedB)
+
+	details["shares"] = o.ConvertStroopValueToReal(op.Amount)
+
+	return details, nil
+}
+
+func (o *LedgerOperation) InvokeHostFunctionDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetInvokeHostFunctionOp()
+	if !ok {
+		return details, fmt.Errorf("could not access InvokeHostFunction info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["function"] = op.HostFunction.Type.String()
+
+	switch op.HostFunction.Type {
+	case xdr.HostFunctionTypeHostFunctionTypeInvokeContract:
+		invokeArgs := op.HostFunction.MustInvokeContract()
+		args := make([]xdr.ScVal, 0, len(invokeArgs.Args)+2)
+		args = append(args, xdr.ScVal{Type: xdr.ScValTypeScvAddress, Address: &invokeArgs.ContractAddress})
+		args = append(args, xdr.ScVal{Type: xdr.ScValTypeScvSymbol, Sym: &invokeArgs.FunctionName})
+		args = append(args, invokeArgs.Args...)
+
+		details["type"] = "invoke_contract"
+
+		contractId, err := invokeArgs.ContractAddress.String()
+		if err != nil {
+			return nil, err
+		}
+
+		details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
+		details["contract_id"] = contractId
+		var contractCodeHash string
+		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+		if ok {
+			details["contract_code_hash"] = contractCodeHash
+		}
+
+		details["parameters"], details["parameters_decoded"] = o.serializeParameters(args)
+
+		if balanceChanges, err := o.parseAssetBalanceChangesFromContractEvents(); err != nil {
+			return nil, err
+		} else {
+			details["asset_balance_changes"] = balanceChanges
+		}
+
+	case xdr.HostFunctionTypeHostFunctionTypeCreateContract:
+		args := op.HostFunction.MustCreateContract()
+		details["type"] = "create_contract"
+
+		details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
+
+		var contractID string
+		contractID, ok = o.Transaction.contractIdFromTxEnvelope()
+		if ok {
+			details["contract_id"] = contractID
+		}
+
+		var contractCodeHash string
+		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+		if ok {
+			details["contract_code_hash"] = contractCodeHash
+		}
+
+		preimageTypeMap := o.switchContractIdPreimageType(args.ContractIdPreimage)
+		for key, val := range preimageTypeMap {
+			if _, ok := preimageTypeMap[key]; ok {
+				details[key] = val
+			}
+		}
+	case xdr.HostFunctionTypeHostFunctionTypeUploadContractWasm:
+		details["type"] = "upload_wasm"
+		details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
+
+		var contractCodeHash string
+		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+		if ok {
+			details["contract_code_hash"] = contractCodeHash
+		}
+	case xdr.HostFunctionTypeHostFunctionTypeCreateContractV2:
+		args := op.HostFunction.MustCreateContractV2()
+		details["type"] = "create_contract_v2"
+
+		details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
+
+		var contractID string
+		contractID, ok = o.Transaction.contractIdFromTxEnvelope()
+		if ok {
+			details["contract_id"] = contractID
+		}
+
+		var contractCodeHash string
+		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+		if ok {
+			details["contract_code_hash"] = contractCodeHash
+		}
+
+		// ConstructorArgs is a list of ScVals
+		// This will initially be handled the same as InvokeContractParams until a different
+		// model is found necessary.
+		constructorArgs := args.ConstructorArgs
+		details["parameters"], details["parameters_decoded"] = o.serializeParameters(constructorArgs)
+
+		preimageTypeMap := o.switchContractIdPreimageType(args.ContractIdPreimage)
+		for key, val := range preimageTypeMap {
+			if _, ok := preimageTypeMap[key]; ok {
+				details[key] = val
+			}
+		}
+	default:
+		panic(fmt.Errorf("unknown host function type: %s", op.HostFunction.Type))
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) ExtendFootprintTtlDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	op, ok := o.Operation.Body.GetExtendFootprintTtlOp()
+	if !ok {
+		return details, fmt.Errorf("could not access ExtendFootprintTtl info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["type"] = "extend_footprint_ttl"
+	details["extend_to"] = int32(op.ExtendTo)
+
+	details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
+
+	var contractID string
+	contractID, ok = o.Transaction.contractIdFromTxEnvelope()
+	if ok {
+		details["contract_id"] = contractID
+	}
+
+	var contractCodeHash string
+	contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+	if ok {
+		details["contract_code_hash"] = contractCodeHash
+	}
+
+	return details, nil
+}
+func (o *LedgerOperation) RestoreFootprintDetails() (map[string]interface{}, error) {
+	details := map[string]interface{}{}
+	_, ok := o.Operation.Body.GetRestoreFootprintOp()
+	if !ok {
+		return details, fmt.Errorf("could not access InvokeHostFunction info for this operation (index %d)", o.OperationIndex)
+	}
+
+	details["type"] = "restore_footprint"
+
+	details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
+
+	var contractID string
+	contractID, ok = o.Transaction.contractIdFromTxEnvelope()
+	if ok {
+		details["contract_id"] = contractID
+	}
+
+	var contractCodeHash string
+	contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+	if ok {
+		details["contract_code_hash"] = contractCodeHash
+	}
+
+	return details, nil
+}
+
+func (o *LedgerOperation) serializeParameters(args []xdr.ScVal) ([]map[string]string, []map[string]string) {
+	params := make([]map[string]string, 0, len(args))
+	paramsDecoded := make([]map[string]string, 0, len(args))
+
+	for _, param := range args {
+		serializedParam := map[string]string{}
+		serializedParam["value"] = "n/a"
+		serializedParam["type"] = "n/a"
+
+		serializedParamDecoded := map[string]string{}
+		serializedParamDecoded["value"] = "n/a"
+		serializedParamDecoded["type"] = "n/a"
+
+		if scValTypeName, ok := param.ArmForSwitch(int32(param.Type)); ok {
+			serializedParam["type"] = scValTypeName
+			serializedParamDecoded["type"] = scValTypeName
+			if raw, err := param.MarshalBinary(); err == nil {
+				serializedParam["value"] = base64.StdEncoding.EncodeToString(raw)
+				serializedParamDecoded["value"] = param.String()
+			}
+		}
+		params = append(params, serializedParam)
+		paramsDecoded = append(paramsDecoded, serializedParamDecoded)
+	}
+
+	return params, paramsDecoded
+}
+
+func (o *LedgerOperation) parseAssetBalanceChangesFromContractEvents() ([]map[string]interface{}, error) {
+	balanceChanges := []map[string]interface{}{}
+
+	diagnosticEvents, err := o.Transaction.GetDiagnosticEvents()
+	if err != nil {
+		// this operation in this context must be an InvokeHostFunctionOp, therefore V3Meta should be present
+		// as it's in same soroban model, so if any err, it's real,
+		return nil, err
+	}
+
+	for _, contractEvent := range o.filterEvents(diagnosticEvents) {
+		// Parse the xdr contract event to contractevents.StellarAssetContractEvent model
+
+		// has some convenience like to/from attributes are expressed in strkey format for accounts(G...) and contracts(C...)
+		if sacEvent, err := contractevents.NewStellarAssetContractEvent(&contractEvent, o.NetworkPassphrase); err == nil {
+			switch sacEvent.GetType() {
+			case contractevents.EventTypeTransfer:
+				transferEvt := sacEvent.(*contractevents.TransferEvent)
+				balanceChanges = append(balanceChanges, o.createSACBalanceChangeEntry(transferEvt.From, transferEvt.To, transferEvt.Amount, transferEvt.Asset, "transfer"))
+			case contractevents.EventTypeMint:
+				mintEvt := sacEvent.(*contractevents.MintEvent)
+				balanceChanges = append(balanceChanges, o.createSACBalanceChangeEntry("", mintEvt.To, mintEvt.Amount, mintEvt.Asset, "mint"))
+			case contractevents.EventTypeClawback:
+				clawbackEvt := sacEvent.(*contractevents.ClawbackEvent)
+				balanceChanges = append(balanceChanges, o.createSACBalanceChangeEntry(clawbackEvt.From, "", clawbackEvt.Amount, clawbackEvt.Asset, "clawback"))
+			case contractevents.EventTypeBurn:
+				burnEvt := sacEvent.(*contractevents.BurnEvent)
+				balanceChanges = append(balanceChanges, o.createSACBalanceChangeEntry(burnEvt.From, "", burnEvt.Amount, burnEvt.Asset, "burn"))
+			}
+		}
+	}
+
+	return balanceChanges, nil
+}
+
+func (o *LedgerOperation) filterEvents(diagnosticEvents []xdr.DiagnosticEvent) []xdr.ContractEvent {
+	var filtered []xdr.ContractEvent
+	for _, diagnosticEvent := range diagnosticEvents {
+		if !diagnosticEvent.InSuccessfulContractCall || diagnosticEvent.Event.Type != xdr.ContractEventTypeContract {
+			continue
+		}
+		filtered = append(filtered, diagnosticEvent.Event)
+	}
+	return filtered
+}
+
+// fromAccount   - strkey format of contract or address
+// toAccount     - strkey format of contract or address, or nillable
+// amountChanged - absolute value that asset balance changed
+// asset         - the fully qualified issuer:code for asset that had balance change
+// changeType    - the type of source sac event that triggered this change
+//
+// return        - a balance changed record expressed as map of key/value's
+func (o *LedgerOperation) createSACBalanceChangeEntry(fromAccount string, toAccount string, amountChanged xdr.Int128Parts, asset xdr.Asset, changeType string) map[string]interface{} {
+	balanceChange := map[string]interface{}{}
+
+	if fromAccount != "" {
+		balanceChange["from"] = fromAccount
+	}
+	if toAccount != "" {
+		balanceChange["to"] = toAccount
+	}
+
+	balanceChange["type"] = changeType
+	balanceChange["amount"] = amount.String128(amountChanged)
+	o.addAssetDetails(balanceChange, asset, "")
+	return balanceChange
+}
+
+// addAssetDetails sets the details for `a` on `result` using keys with `prefix`
+func (o *LedgerOperation) addAssetDetails(result map[string]interface{}, a xdr.Asset, prefix string) error {
+	var (
+		assetType string
+		code      string
+		issuer    string
+	)
+	err := a.Extract(&assetType, &code, &issuer)
+	if err != nil {
+		err = fmt.Errorf("xdr.Asset.Extract error: %w", err)
+		return err
+	}
+	result[prefix+"asset_type"] = assetType
+
+	if a.Type == xdr.AssetTypeAssetTypeNative {
+		return nil
+	}
+
+	result[prefix+"asset_code"] = code
+	result[prefix+"asset_issuer"] = issuer
+	return nil
+}
+
+func (o *LedgerOperation) switchContractIdPreimageType(contractIdPreimage xdr.ContractIdPreimage) map[string]interface{} {
+	details := map[string]interface{}{}
+
+	switch contractIdPreimage.Type {
+	case xdr.ContractIdPreimageTypeContractIdPreimageFromAddress:
+		fromAddress := contractIdPreimage.MustFromAddress()
+		address, err := fromAddress.Address.String()
+		if err != nil {
+			panic(fmt.Errorf("error obtaining address for: %s", contractIdPreimage.Type))
+		}
+		details["from"] = "address"
+		details["address"] = address
+	case xdr.ContractIdPreimageTypeContractIdPreimageFromAsset:
+		details["from"] = "asset"
+		details["asset"] = contractIdPreimage.MustFromAsset().StringCanonical()
+	default:
+		panic(fmt.Errorf("unknown contract id type: %s", contractIdPreimage.Type))
+	}
+
+	return details
+}
+
+func (o *LedgerOperation) ConvertStroopValueToReal(input xdr.Int64) float64 {
+	output, _ := big.NewRat(int64(input), int64(10000000)).Float64()
+	return output
+}
+
+func (o *LedgerOperation) FormatPrefix(p string) string {
+	if p != "" {
+		p += "_"
+	}
+	return p
+}
+
+func (o *LedgerOperation) FarmHashAsset(assetCode, assetIssuer, assetType string) int64 {
+	asset := fmt.Sprintf("%s%s%s", assetCode, assetIssuer, assetType)
+	hash := farm.Fingerprint64([]byte(asset))
+
+	return int64(hash)
+}
+
+// Path is a representation of an asset without an ID that forms part of a path in a path payment
+type Path struct {
+	AssetCode   string `json:"asset_code"`
+	AssetIssuer string `json:"asset_issuer"`
+	AssetType   string `json:"asset_type"`
+}
+
+func (o *LedgerOperation) TransformPath(initialPath []xdr.Asset) []Path {
+	if len(initialPath) == 0 {
+		return nil
+	}
+	var path = make([]Path, 0)
+	for _, pathAsset := range initialPath {
+		var assetType, code, issuer string
+		err := pathAsset.Extract(&assetType, &code, &issuer)
+		if err != nil {
+			return nil
+		}
+
+		path = append(path, Path{
+			AssetType:   assetType,
+			AssetIssuer: issuer,
+			AssetCode:   code,
+		})
+	}
+	return path
+}
+
+type Price struct {
+	Numerator   int32 `json:"n"`
+	Denominator int32 `json:"d"`
+}
+
+func (o *LedgerOperation) PoolIDToString(id xdr.PoolId) string {
+	return xdr.Hash(id).HexString()
+}
+
+type Claimant struct {
+	Destination string             `json:"destination"`
+	Predicate   xdr.ClaimPredicate `json:"predicate"`
+}
+
+func (o *LedgerOperation) TransformClaimants(claimants []xdr.Claimant) []Claimant {
+	var transformed []Claimant
+	for _, c := range claimants {
+		switch c.Type {
+		case 0:
+			transformed = append(transformed, Claimant{
+				Destination: c.V0.Destination.Address(),
+				Predicate:   c.V0.Predicate,
+			})
+		}
+	}
+	return transformed
+}
+
+type SponsorshipOutput struct {
+	Operation      xdr.Operation
+	OperationIndex uint32
+}
+
+type LiquidityPoolDelta struct {
+	ReserveA        xdr.Int64
+	ReserveB        xdr.Int64
+	TotalPoolShares xdr.Int64
+}
diff --git a/ingest/ledger_operation_test.go b/ingest/ledger_operation_test.go
new file mode 100644
index 0000000000..18c4760219
--- /dev/null
+++ b/ingest/ledger_operation_test.go
@@ -0,0 +1,1474 @@
+package ingest
+
+import (
+	"testing"
+
+	"github.com/stellar/go/xdr"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestOperation(t *testing.T) {
+	testOutput := resultTestOutput()
+	for i, op := range operationTestInput() {
+		ledgerOperation := LedgerOperation{
+			OperationIndex:    int32(i),
+			Operation:         op,
+			Transaction:       transactionTestInput(),
+			NetworkPassphrase: "",
+		}
+
+		result, err := ledgerOperation.OperationDetails()
+		assert.Equal(t, testOutput[i].err, err)
+		assert.Equal(t, testOutput[i].result, result)
+	}
+}
+
+func ledgerTestInput() (lcm xdr.LedgerCloseMeta) {
+	lcm = xdr.LedgerCloseMeta{
+		V: 1,
+		V1: &xdr.LedgerCloseMetaV1{
+			LedgerHeader: xdr.LedgerHeaderHistoryEntry{
+				Header: xdr.LedgerHeader{
+					LedgerSeq:     30578981,
+					LedgerVersion: 22,
+				},
+			},
+		},
+	}
+
+	return lcm
+}
+
+func transactionTestInput() *LedgerTransaction {
+	testAccountAddress := "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"
+	testAccountID, _ := xdr.AddressToAccountId(testAccountAddress)
+	dummyBool := true
+
+	ed25519 := xdr.Uint256([32]byte{0x11, 0x22, 0x33})
+	muxedAccount := xdr.MuxedAccount{
+		Type:    256,
+		Ed25519: &ed25519,
+		Med25519: &xdr.MuxedAccountMed25519{
+			Id:      xdr.Uint64(123),
+			Ed25519: ed25519,
+		},
+	}
+
+	memoText := "test memo"
+	minSeqNum := xdr.SequenceNumber(123)
+
+	transaction := &LedgerTransaction{
+		Index: 1,
+		Envelope: xdr.TransactionEnvelope{
+			Type: xdr.EnvelopeTypeEnvelopeTypeTx,
+			V1: &xdr.TransactionV1Envelope{
+				Signatures: []xdr.DecoratedSignature{
+					{
+						Signature: []byte{0x11, 0x22},
+					},
+				},
+				Tx: xdr.Transaction{
+					SourceAccount: muxedAccount,
+					SeqNum:        xdr.SequenceNumber(30578981),
+					Fee:           xdr.Uint32(4560),
+					Operations: []xdr.Operation{
+						{
+							SourceAccount: &muxedAccount,
+							Body:          xdr.OperationBody{},
+						},
+						{
+							SourceAccount: &muxedAccount,
+							Body:          xdr.OperationBody{},
+						},
+						{
+							SourceAccount: &muxedAccount,
+							Body:          xdr.OperationBody{},
+						},
+					},
+					Memo: xdr.Memo{
+						Type: xdr.MemoTypeMemoText,
+						Text: &memoText,
+					},
+					Cond: xdr.Preconditions{
+						Type: 2,
+						V2: &xdr.PreconditionsV2{
+							TimeBounds: &xdr.TimeBounds{
+								MinTime: xdr.TimePoint(1),
+								MaxTime: xdr.TimePoint(10),
+							},
+							LedgerBounds: &xdr.LedgerBounds{
+								MinLedger: 2,
+								MaxLedger: 20,
+							},
+							MinSeqNum:       &minSeqNum,
+							MinSeqAge:       456,
+							MinSeqLedgerGap: 789,
+						},
+					},
+					Ext: xdr.TransactionExt{
+						V: 1,
+						SorobanData: &xdr.SorobanTransactionData{
+							Resources: xdr.SorobanResources{
+								Instructions: 123,
+								ReadBytes:    456,
+								WriteBytes:   789,
+								Footprint: xdr.LedgerFootprint{
+									ReadOnly: []xdr.LedgerKey{
+										{
+											Type: 6,
+											ContractData: &xdr.LedgerKeyContractData{
+												Contract: xdr.ScAddress{
+													Type:       1,
+													ContractId: &xdr.Hash{0x12, 0x34},
+												},
+												Key: xdr.ScVal{
+													Type: 0,
+													B:    &dummyBool,
+												},
+											},
+										},
+									},
+								},
+							},
+							ResourceFee: 1234,
+						},
+					},
+				},
+			},
+		},
+		Result: xdr.TransactionResultPair{
+			TransactionHash: xdr.Hash{0x11, 0x22, 0x33},
+			Result: xdr.TransactionResult{
+				FeeCharged: xdr.Int64(789),
+				Result: xdr.TransactionResultResult{
+					Code: 0,
+					Results: &[]xdr.OperationResult{
+						{},
+						{},
+						{},
+						{
+							Code: 0,
+							Tr: &xdr.OperationResultTr{
+								Type: 2,
+								PathPaymentStrictReceiveResult: &xdr.PathPaymentStrictReceiveResult{
+									Code:     0,
+									Success:  &xdr.PathPaymentStrictReceiveResultSuccess{},
+									NoIssuer: &xdr.Asset{},
+								},
+							},
+						},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{
+							Code: 0,
+							Tr: &xdr.OperationResultTr{
+								Type: 13,
+								PathPaymentStrictSendResult: &xdr.PathPaymentStrictSendResult{
+									Code: 0,
+									Success: &xdr.PathPaymentStrictSendResultSuccess{
+										Last: xdr.SimplePaymentResult{
+											Amount: 640000000,
+										},
+									},
+									NoIssuer: &xdr.Asset{},
+								},
+							},
+						},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{},
+						{
+							Code: 0,
+							Tr: &xdr.OperationResultTr{
+								Type: 22,
+								LiquidityPoolDepositResult: &xdr.LiquidityPoolDepositResult{
+									Code: 0,
+								},
+							},
+						},
+						{
+							Code: 0,
+							Tr: &xdr.OperationResultTr{
+								Type: 23,
+								LiquidityPoolWithdrawResult: &xdr.LiquidityPoolWithdrawResult{
+									Code: 0,
+								},
+							},
+						},
+						{},
+					},
+				},
+			},
+		},
+		FeeChanges: xdr.LedgerEntryChanges{
+			{
+				Type: xdr.LedgerEntryChangeTypeLedgerEntryState,
+				State: &xdr.LedgerEntry{
+					Data: xdr.LedgerEntryData{
+						Type: xdr.LedgerEntryTypeAccount,
+						Account: &xdr.AccountEntry{
+							AccountId: xdr.AccountId{
+								Type:    0,
+								Ed25519: &ed25519,
+							},
+							Balance: 1000,
+						},
+					},
+				},
+			},
+			{},
+		},
+		UnsafeMeta: xdr.TransactionMeta{
+			V: 3,
+			V3: &xdr.TransactionMetaV3{
+				TxChangesAfter: xdr.LedgerEntryChanges{},
+				SorobanMeta: &xdr.SorobanTransactionMeta{
+					Ext: xdr.SorobanTransactionMetaExt{
+						V: 1,
+						V1: &xdr.SorobanTransactionMetaExtV1{
+							TotalNonRefundableResourceFeeCharged: 321,
+							TotalRefundableResourceFeeCharged:    123,
+							RentFeeCharged:                       456,
+						},
+					},
+				},
+				Operations: []xdr.OperationMeta{
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{},
+					{
+						Changes: []xdr.LedgerEntryChange{
+							{
+								Type: 3,
+								State: &xdr.LedgerEntry{
+									Data: xdr.LedgerEntryData{
+										Type: 5,
+										LiquidityPool: &xdr.LiquidityPoolEntry{
+											LiquidityPoolId: xdr.PoolId{1, 2, 3, 4, 5, 6, 7, 8, 9},
+											Body: xdr.LiquidityPoolEntryBody{
+												Type: 0,
+												ConstantProduct: &xdr.LiquidityPoolEntryConstantProduct{
+													Params: xdr.LiquidityPoolConstantProductParameters{
+														AssetA: xdr.Asset{
+															Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+															AlphaNum4: &xdr.AlphaNum4{
+																AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x44, 0x54}),
+																Issuer:    testAccountID,
+															},
+														},
+														AssetB: xdr.Asset{
+															Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+															AlphaNum4: &xdr.AlphaNum4{
+																AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x44, 0x54}),
+																Issuer:    testAccountID,
+															},
+														},
+														Fee: 1,
+													},
+													ReserveA:                 1,
+													ReserveB:                 1,
+													TotalPoolShares:          1,
+													PoolSharesTrustLineCount: 1,
+												},
+											},
+										},
+									},
+								},
+							},
+							{
+								Type: 1,
+								Updated: &xdr.LedgerEntry{
+									Data: xdr.LedgerEntryData{
+										Type: 5,
+										LiquidityPool: &xdr.LiquidityPoolEntry{
+											LiquidityPoolId: xdr.PoolId{1, 2, 3, 4, 5, 6, 7, 8, 9},
+											Body: xdr.LiquidityPoolEntryBody{
+												Type: 0,
+												ConstantProduct: &xdr.LiquidityPoolEntryConstantProduct{
+													Params: xdr.LiquidityPoolConstantProductParameters{
+														AssetA: xdr.Asset{
+															Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+															AlphaNum4: &xdr.AlphaNum4{
+																AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x44, 0x54}),
+																Issuer:    testAccountID,
+															},
+														},
+														AssetB: xdr.Asset{
+															Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+															AlphaNum4: &xdr.AlphaNum4{
+																AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x44, 0x54}),
+																Issuer:    testAccountID,
+															},
+														},
+														Fee: 1,
+													},
+													ReserveA:                 2,
+													ReserveB:                 2,
+													TotalPoolShares:          2,
+													PoolSharesTrustLineCount: 1,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+					{
+						Changes: []xdr.LedgerEntryChange{
+							{
+								Type: 3,
+								State: &xdr.LedgerEntry{
+									Data: xdr.LedgerEntryData{
+										Type: 5,
+										LiquidityPool: &xdr.LiquidityPoolEntry{
+											LiquidityPoolId: xdr.PoolId{1, 2, 3, 4, 5, 6, 7, 8, 9},
+											Body: xdr.LiquidityPoolEntryBody{
+												Type: 0,
+												ConstantProduct: &xdr.LiquidityPoolEntryConstantProduct{
+													Params: xdr.LiquidityPoolConstantProductParameters{
+														AssetA: xdr.Asset{
+															Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+															AlphaNum4: &xdr.AlphaNum4{
+																AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x44, 0x54}),
+																Issuer:    testAccountID,
+															},
+														},
+														AssetB: xdr.Asset{
+															Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+															AlphaNum4: &xdr.AlphaNum4{
+																AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x44, 0x54}),
+																Issuer:    testAccountID,
+															},
+														},
+														Fee: 1,
+													},
+													ReserveA:                 1,
+													ReserveB:                 1,
+													TotalPoolShares:          1,
+													PoolSharesTrustLineCount: 1,
+												},
+											},
+										},
+									},
+								},
+							},
+							{
+								Type: 1,
+								Updated: &xdr.LedgerEntry{
+									Data: xdr.LedgerEntryData{
+										Type: 5,
+										LiquidityPool: &xdr.LiquidityPoolEntry{
+											LiquidityPoolId: xdr.PoolId{1, 2, 3, 4, 5, 6, 7, 8, 9},
+											Body: xdr.LiquidityPoolEntryBody{
+												Type: 0,
+												ConstantProduct: &xdr.LiquidityPoolEntryConstantProduct{
+													Params: xdr.LiquidityPoolConstantProductParameters{
+														AssetA: xdr.Asset{
+															Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+															AlphaNum4: &xdr.AlphaNum4{
+																AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x44, 0x54}),
+																Issuer:    testAccountID,
+															},
+														},
+														AssetB: xdr.Asset{
+															Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+															AlphaNum4: &xdr.AlphaNum4{
+																AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x44, 0x54}),
+																Issuer:    testAccountID,
+															},
+														},
+														Fee: 1,
+													},
+													ReserveA:                 2,
+													ReserveB:                 2,
+													TotalPoolShares:          2,
+													PoolSharesTrustLineCount: 1,
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		LedgerVersion: 22,
+		Ledger:        ledgerTestInput(),
+		Hash:          xdr.Hash{},
+	}
+
+	return transaction
+}
+
+type testOutput struct {
+	err    error
+	result map[string]interface{}
+}
+
+func operationTestInput() []xdr.Operation {
+	testAccountAddress := "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"
+	testAccountID, _ := xdr.AddressToAccountId(testAccountAddress)
+	testAccountMuxed := testAccountID.ToMuxedAccount()
+
+	sourceAccountAddress := "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN"
+	sourceAccountID, _ := xdr.AddressToAccountId(sourceAccountAddress)
+	sourceAccountMuxed := sourceAccountID.ToMuxedAccount()
+
+	usdtAsset := xdr.Asset{
+		Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+		AlphaNum4: &xdr.AlphaNum4{
+			AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x44, 0x54}),
+			Issuer:    testAccountID,
+		},
+	}
+	assetCode, _ := usdtAsset.ToAssetCode("USDT")
+	nativeAsset := xdr.MustNewNativeAsset()
+
+	clearFlags := xdr.Uint32(3)
+	setFlags := xdr.Uint32(4)
+	masterWeight := xdr.Uint32(3)
+	lowThresh := xdr.Uint32(1)
+	medThresh := xdr.Uint32(3)
+	highThresh := xdr.Uint32(5)
+	homeDomain := xdr.String32("2019=DRA;n-test")
+	signerKey, _ := xdr.NewSignerKey(xdr.SignerKeyTypeSignerKeyTypeEd25519, xdr.Uint256([32]byte{}))
+	signer := xdr.Signer{
+		Key:    signerKey,
+		Weight: xdr.Uint32(1),
+	}
+
+	usdtChangeTrustAsset := xdr.ChangeTrustAsset{
+		Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+		AlphaNum4: &xdr.AlphaNum4{
+			AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x53, 0x44}),
+			Issuer:    testAccountID,
+		},
+	}
+
+	usdtLiquidityPoolShare := xdr.ChangeTrustAsset{
+		Type: xdr.AssetTypeAssetTypePoolShare,
+		LiquidityPool: &xdr.LiquidityPoolParameters{
+			Type: xdr.LiquidityPoolTypeLiquidityPoolConstantProduct,
+			ConstantProduct: &xdr.LiquidityPoolConstantProductParameters{
+				AssetA: nativeAsset,
+				AssetB: usdtAsset,
+				Fee:    30,
+			},
+		},
+	}
+
+	dataValue := xdr.DataValue([]byte{0x76, 0x61, 0x6c, 0x75, 0x65})
+
+	testClaimant := xdr.Claimant{
+		Type: xdr.ClaimantTypeClaimantTypeV0,
+		V0: &xdr.ClaimantV0{
+			Destination: testAccountID,
+			Predicate: xdr.ClaimPredicate{
+				Type: xdr.ClaimPredicateTypeClaimPredicateUnconditional,
+			},
+		},
+	}
+
+	claimableBalance := xdr.ClaimableBalanceId{
+		Type: xdr.ClaimableBalanceIdTypeClaimableBalanceIdTypeV0,
+		V0:   &xdr.Hash{1, 2, 3, 4, 5, 6, 7, 8, 9},
+	}
+
+	contractHash := xdr.Hash{0x12, 0x34, 0x56, 0x78}
+	salt := [32]byte{0x12, 0x34, 0x56}
+	wasm := []byte{0x12, 0x34}
+	dummyBool := true
+
+	operation := []xdr.Operation{
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeCreateAccount,
+				CreateAccountOp: &xdr.CreateAccountOp{
+					StartingBalance: 25000000,
+					Destination:     testAccountID,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypePayment,
+				PaymentOp: &xdr.PaymentOp{
+					Destination: testAccountMuxed,
+					Asset:       usdtAsset,
+					Amount:      350000000,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypePayment,
+				PaymentOp: &xdr.PaymentOp{
+					Destination: testAccountMuxed,
+					Asset:       nativeAsset,
+					Amount:      350000000,
+				},
+			},
+		},
+		{
+			SourceAccount: &sourceAccountMuxed,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypePathPaymentStrictReceive,
+				PathPaymentStrictReceiveOp: &xdr.PathPaymentStrictReceiveOp{
+					SendAsset:   nativeAsset,
+					SendMax:     8951495900,
+					Destination: testAccountMuxed,
+					DestAsset:   nativeAsset,
+					DestAmount:  8951495900,
+					Path:        []xdr.Asset{usdtAsset},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeManageSellOffer,
+				ManageSellOfferOp: &xdr.ManageSellOfferOp{
+					Selling: usdtAsset,
+					Buying:  nativeAsset,
+					Amount:  765860000,
+					Price: xdr.Price{
+						N: 128523,
+						D: 250000,
+					},
+					OfferId: 0,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeCreatePassiveSellOffer,
+				CreatePassiveSellOfferOp: &xdr.CreatePassiveSellOfferOp{
+					Selling: nativeAsset,
+					Buying:  usdtAsset,
+					Amount:  631595000,
+					Price: xdr.Price{
+						N: 99583200,
+						D: 1257990000,
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeSetOptions,
+				SetOptionsOp: &xdr.SetOptionsOp{
+					InflationDest: &testAccountID,
+					ClearFlags:    &clearFlags,
+					SetFlags:      &setFlags,
+					MasterWeight:  &masterWeight,
+					LowThreshold:  &lowThresh,
+					MedThreshold:  &medThresh,
+					HighThreshold: &highThresh,
+					HomeDomain:    &homeDomain,
+					Signer:        &signer,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeChangeTrust,
+				ChangeTrustOp: &xdr.ChangeTrustOp{
+					Line:  usdtChangeTrustAsset,
+					Limit: xdr.Int64(500000000000000000),
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeChangeTrust,
+				ChangeTrustOp: &xdr.ChangeTrustOp{
+					Line:  usdtLiquidityPoolShare,
+					Limit: xdr.Int64(500000000000000000),
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeAllowTrust,
+				AllowTrustOp: &xdr.AllowTrustOp{
+					Trustor:   testAccountID,
+					Asset:     assetCode,
+					Authorize: xdr.Uint32(1),
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type:        xdr.OperationTypeAccountMerge,
+				Destination: &testAccountMuxed,
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeInflation,
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeManageData,
+				ManageDataOp: &xdr.ManageDataOp{
+					DataName:  "test",
+					DataValue: &dataValue,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeBumpSequence,
+				BumpSequenceOp: &xdr.BumpSequenceOp{
+					BumpTo: xdr.SequenceNumber(100),
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeManageBuyOffer,
+				ManageBuyOfferOp: &xdr.ManageBuyOfferOp{
+					Selling:   usdtAsset,
+					Buying:    nativeAsset,
+					BuyAmount: 7654501001,
+					Price: xdr.Price{
+						N: 635863285,
+						D: 1818402817,
+					},
+					OfferId: 100,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypePathPaymentStrictSend,
+				PathPaymentStrictSendOp: &xdr.PathPaymentStrictSendOp{
+					SendAsset:   nativeAsset,
+					SendAmount:  1598182,
+					Destination: testAccountMuxed,
+					DestAsset:   nativeAsset,
+					DestMin:     4280460538,
+					Path:        []xdr.Asset{usdtAsset},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeCreateClaimableBalance,
+				CreateClaimableBalanceOp: &xdr.CreateClaimableBalanceOp{
+					Asset:     usdtAsset,
+					Amount:    1234567890000,
+					Claimants: []xdr.Claimant{testClaimant},
+				},
+			},
+		},
+		{
+			SourceAccount: &sourceAccountMuxed,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeClaimClaimableBalance,
+				ClaimClaimableBalanceOp: &xdr.ClaimClaimableBalanceOp{
+					BalanceId: claimableBalance,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeBeginSponsoringFutureReserves,
+				BeginSponsoringFutureReservesOp: &xdr.BeginSponsoringFutureReservesOp{
+					SponsoredId: testAccountID,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeRevokeSponsorship,
+				RevokeSponsorshipOp: &xdr.RevokeSponsorshipOp{
+					Type: xdr.RevokeSponsorshipTypeRevokeSponsorshipSigner,
+					Signer: &xdr.RevokeSponsorshipOpSigner{
+						AccountId: testAccountID,
+						SignerKey: signer.Key,
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeRevokeSponsorship,
+				RevokeSponsorshipOp: &xdr.RevokeSponsorshipOp{
+					Type: xdr.RevokeSponsorshipTypeRevokeSponsorshipLedgerEntry,
+					LedgerKey: &xdr.LedgerKey{
+						Type: xdr.LedgerEntryTypeAccount,
+						Account: &xdr.LedgerKeyAccount{
+							AccountId: testAccountID,
+						},
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeRevokeSponsorship,
+				RevokeSponsorshipOp: &xdr.RevokeSponsorshipOp{
+					Type: xdr.RevokeSponsorshipTypeRevokeSponsorshipLedgerEntry,
+					LedgerKey: &xdr.LedgerKey{
+						Type: xdr.LedgerEntryTypeClaimableBalance,
+						ClaimableBalance: &xdr.LedgerKeyClaimableBalance{
+							BalanceId: claimableBalance,
+						},
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeRevokeSponsorship,
+				RevokeSponsorshipOp: &xdr.RevokeSponsorshipOp{
+					Type: xdr.RevokeSponsorshipTypeRevokeSponsorshipLedgerEntry,
+					LedgerKey: &xdr.LedgerKey{
+						Type: xdr.LedgerEntryTypeData,
+						Data: &xdr.LedgerKeyData{
+							AccountId: testAccountID,
+							DataName:  "test",
+						},
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeRevokeSponsorship,
+				RevokeSponsorshipOp: &xdr.RevokeSponsorshipOp{
+					Type: xdr.RevokeSponsorshipTypeRevokeSponsorshipLedgerEntry,
+					LedgerKey: &xdr.LedgerKey{
+						Type: xdr.LedgerEntryTypeOffer,
+						Offer: &xdr.LedgerKeyOffer{
+							SellerId: testAccountID,
+							OfferId:  100,
+						},
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeRevokeSponsorship,
+				RevokeSponsorshipOp: &xdr.RevokeSponsorshipOp{
+					Type: xdr.RevokeSponsorshipTypeRevokeSponsorshipLedgerEntry,
+					LedgerKey: &xdr.LedgerKey{
+						Type: xdr.LedgerEntryTypeTrustline,
+						TrustLine: &xdr.LedgerKeyTrustLine{
+							AccountId: testAccountID,
+							Asset: xdr.TrustLineAsset{
+								Type: xdr.AssetTypeAssetTypeCreditAlphanum4,
+								AlphaNum4: &xdr.AlphaNum4{
+									AssetCode: xdr.AssetCode4([4]byte{0x55, 0x53, 0x54, 0x54}),
+									Issuer:    testAccountID,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeRevokeSponsorship,
+				RevokeSponsorshipOp: &xdr.RevokeSponsorshipOp{
+					Type: xdr.RevokeSponsorshipTypeRevokeSponsorshipLedgerEntry,
+					LedgerKey: &xdr.LedgerKey{
+						Type: xdr.LedgerEntryTypeLiquidityPool,
+						LiquidityPool: &xdr.LedgerKeyLiquidityPool{
+							LiquidityPoolId: xdr.PoolId{1, 2, 3, 4, 5, 6, 7, 8, 9},
+						},
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeClawback,
+				ClawbackOp: &xdr.ClawbackOp{
+					Asset:  usdtAsset,
+					From:   testAccountMuxed,
+					Amount: 1598182,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeClawbackClaimableBalance,
+				ClawbackClaimableBalanceOp: &xdr.ClawbackClaimableBalanceOp{
+					BalanceId: claimableBalance,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeSetTrustLineFlags,
+				SetTrustLineFlagsOp: &xdr.SetTrustLineFlagsOp{
+					Trustor:    testAccountID,
+					Asset:      usdtAsset,
+					SetFlags:   setFlags,
+					ClearFlags: clearFlags,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeLiquidityPoolDeposit,
+				LiquidityPoolDepositOp: &xdr.LiquidityPoolDepositOp{
+					LiquidityPoolId: xdr.PoolId{1, 2, 3, 4, 5, 6, 7, 8, 9},
+					MaxAmountA:      1000,
+					MaxAmountB:      100,
+					MinPrice: xdr.Price{
+						N: 1,
+						D: 1000000,
+					},
+					MaxPrice: xdr.Price{
+						N: 1000000,
+						D: 1,
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeLiquidityPoolWithdraw,
+				LiquidityPoolWithdrawOp: &xdr.LiquidityPoolWithdrawOp{
+					LiquidityPoolId: xdr.PoolId{1, 2, 3, 4, 5, 6, 7, 8, 9},
+					Amount:          4,
+					MinAmountA:      1,
+					MinAmountB:      1,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeInvokeHostFunction,
+				InvokeHostFunctionOp: &xdr.InvokeHostFunctionOp{
+					HostFunction: xdr.HostFunction{
+						Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract,
+						InvokeContract: &xdr.InvokeContractArgs{
+							ContractAddress: xdr.ScAddress{
+								Type:       xdr.ScAddressTypeScAddressTypeContract,
+								ContractId: &contractHash,
+							},
+							FunctionName: "test",
+							Args:         []xdr.ScVal{},
+						},
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeInvokeHostFunction,
+				InvokeHostFunctionOp: &xdr.InvokeHostFunctionOp{
+					HostFunction: xdr.HostFunction{
+						Type: xdr.HostFunctionTypeHostFunctionTypeCreateContract,
+						CreateContract: &xdr.CreateContractArgs{
+							ContractIdPreimage: xdr.ContractIdPreimage{
+								Type: xdr.ContractIdPreimageTypeContractIdPreimageFromAddress,
+								FromAddress: &xdr.ContractIdPreimageFromAddress{
+									Address: xdr.ScAddress{
+										Type:       xdr.ScAddressTypeScAddressTypeContract,
+										ContractId: &contractHash,
+									},
+									Salt: salt,
+								},
+							},
+							Executable: xdr.ContractExecutable{},
+						},
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeInvokeHostFunction,
+				InvokeHostFunctionOp: &xdr.InvokeHostFunctionOp{
+					HostFunction: xdr.HostFunction{
+						Type: xdr.HostFunctionTypeHostFunctionTypeCreateContract,
+						CreateContract: &xdr.CreateContractArgs{
+							ContractIdPreimage: xdr.ContractIdPreimage{
+								Type:      xdr.ContractIdPreimageTypeContractIdPreimageFromAsset,
+								FromAsset: &usdtAsset,
+							},
+							Executable: xdr.ContractExecutable{},
+						},
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeInvokeHostFunction,
+				InvokeHostFunctionOp: &xdr.InvokeHostFunctionOp{
+					HostFunction: xdr.HostFunction{
+						Type: xdr.HostFunctionTypeHostFunctionTypeCreateContractV2,
+						CreateContractV2: &xdr.CreateContractArgsV2{
+							ContractIdPreimage: xdr.ContractIdPreimage{
+								Type:      xdr.ContractIdPreimageTypeContractIdPreimageFromAsset,
+								FromAsset: &usdtAsset,
+							},
+							Executable: xdr.ContractExecutable{},
+							ConstructorArgs: []xdr.ScVal{
+								{
+									Type: xdr.ScValTypeScvBool,
+									B:    &dummyBool,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeInvokeHostFunction,
+				InvokeHostFunctionOp: &xdr.InvokeHostFunctionOp{
+					HostFunction: xdr.HostFunction{
+						Type: xdr.HostFunctionTypeHostFunctionTypeUploadContractWasm,
+						Wasm: &wasm,
+					},
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeExtendFootprintTtl,
+				ExtendFootprintTtlOp: &xdr.ExtendFootprintTtlOp{
+					Ext: xdr.ExtensionPoint{
+						V: 0,
+					},
+					ExtendTo: 1234,
+				},
+			},
+		},
+		{
+			SourceAccount: nil,
+			Body: xdr.OperationBody{
+				Type: xdr.OperationTypeRestoreFootprint,
+				RestoreFootprintOp: &xdr.RestoreFootprintOp{
+					Ext: xdr.ExtensionPoint{
+						V: 0,
+					},
+				},
+			},
+		},
+	}
+
+	return operation
+}
+
+func resultTestOutput() []testOutput {
+	output := []testOutput{
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"account":          "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"funder":           "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				"funder_muxed":     "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				"funder_muxed_id":  uint64(123),
+				"starting_balance": 2.5},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"amount":        float64(35),
+				"asset_code":    "USDT",
+				"asset_id":      int64(-8205667356306085451),
+				"asset_issuer":  "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"asset_type":    "credit_alphanum4",
+				"from":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				"from_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				"from_muxed_id": uint64(123),
+				"to":            "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"amount":        float64(35),
+				"asset_id":      int64(-5706705804583548011),
+				"asset_type":    "native",
+				"from":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				"from_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				"from_muxed_id": uint64(123),
+				"to":            "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"amount":     895.14959,
+				"asset_id":   int64(-5706705804583548011),
+				"asset_type": "native",
+				"from":       "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN",
+				"path": []Path{Path{AssetCode: "USDT",
+					AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+					AssetType:   "credit_alphanum4"}},
+				"source_amount":     float64(0),
+				"source_asset_id":   int64(-5706705804583548011),
+				"source_asset_type": "native",
+				"source_max":        895.14959,
+				"to":                "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"amount":            float64(76.586),
+				"buying_asset_id":   int64(-5706705804583548011),
+				"buying_asset_type": "native",
+				"offer_id":          int64(0),
+				"price":             0.514092,
+				"price_r": Price{Numerator: 128523,
+					Denominator: 250000},
+				"selling_asset_code":   "USDT",
+				"selling_asset_id":     int64(-8205667356306085451),
+				"selling_asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"selling_asset_type":   "credit_alphanum4"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"amount":              float64(63.1595),
+				"buying_asset_code":   "USDT",
+				"buying_asset_id":     int64(-8205667356306085451),
+				"buying_asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"buying_asset_type":   "credit_alphanum4",
+				"price":               0.0791606,
+				"price_r": Price{Numerator: 99583200,
+					Denominator: 1257990000},
+				"selling_asset_id":   int64(-5706705804583548011),
+				"selling_asset_type": "native"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"clear_flags": []int32{1,
+					2},
+				"clear_flags_s": []string{"auth_required",
+					"auth_revocable"},
+				"high_threshold":    uint32(5),
+				"home_domain":       "2019=DRA;n-test",
+				"inflation_dest":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"low_threshold":     uint32(1),
+				"master_key_weight": uint32(3),
+				"med_threshold":     uint32(3),
+				"set_flags":         []int32{4},
+				"set_flags_s":       []string{"auth_immutable"},
+				"signer_key":        "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
+				"signer_weight":     uint32(1)},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"asset_code":       "USSD",
+				"asset_id":         int64(6690054458235693884),
+				"asset_issuer":     "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"asset_type":       "credit_alphanum4",
+				"limit":            5e+10,
+				"trustee":          "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"trustor":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				"trustor_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				"trustor_muxed_id": uint64(123)},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"asset_type":        "liquidity_pool_shares",
+				"limit":             5e+10,
+				"liquidity_pool_id": "1c261d6c75930204a73b480c3020ab525e9be48ce93de6194cf69fb06f07452d",
+				"trustor":           "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				"trustor_muxed":     "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				"trustor_muxed_id":  uint64(123)},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"asset_code":       "USDT",
+				"asset_id":         int64(8181787832768848499),
+				"asset_issuer":     "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				"asset_type":       "credit_alphanum4",
+				"authorize":        true,
+				"trustee":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				"trustee_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				"trustee_muxed_id": uint64(123),
+				"trustor":          "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"account":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				"account_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				"account_muxed_id": uint64(123),
+				"into":             "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+		},
+		{
+			err:    nil,
+			result: map[string]interface{}{},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"name":  "test",
+				"value": "dmFsdWU=",
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"bump_to": "100",
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"amount":            float64(765.4501001),
+				"buying_asset_id":   int64(-5706705804583548011),
+				"buying_asset_type": "native",
+				"offer_id":          int64(100),
+				"price":             0.3496823,
+				"price_r": Price{Numerator: 635863285,
+					Denominator: 1818402817},
+				"selling_asset_code":   "USDT",
+				"selling_asset_id":     int64(-8205667356306085451),
+				"selling_asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"selling_asset_type":   "credit_alphanum4"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"amount":          float64(64),
+				"asset_id":        int64(-5706705804583548011),
+				"asset_type":      "native",
+				"destination_min": "428.0460538",
+				"from":            "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				"from_muxed":      "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				"from_muxed_id":   uint64(123),
+				"path": []Path{{AssetCode: "USDT",
+					AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+					AssetType:   "credit_alphanum4"}},
+				"source_amount":     0.1598182,
+				"source_asset_id":   int64(-5706705804583548011),
+				"source_asset_type": "native",
+				"to":                "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"amount": 123456.789,
+				"asset":  "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"claimants": []Claimant{{Destination: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+					Predicate: xdr.ClaimPredicate{Type: 0,
+						AndPredicates: (*[]xdr.ClaimPredicate)(nil),
+						OrPredicates:  (*[]xdr.ClaimPredicate)(nil),
+						NotPredicate:  (**xdr.ClaimPredicate)(nil),
+						AbsBefore:     (*xdr.Int64)(nil),
+						RelBefore:     (*xdr.Int64)(nil)}}}},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000",
+				"claimant":   "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"sponsored_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"signer_account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"signer_key":        "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"claimable_balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"data_account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"data_name":       "test"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"offer_id": int64(100)},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"trustline_account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"trustline_asset":      "USTT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"liquidity_pool_id": "0102030405060708090000000000000000000000000000000000000000000000"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"amount":       0.1598182,
+				"asset_code":   "USDT",
+				"asset_id":     int64(-8205667356306085451),
+				"asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"asset_type":   "credit_alphanum4",
+				"from":         "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000"},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"asset_code":    "USDT",
+				"asset_id":      int64(-8205667356306085451),
+				"asset_issuer":  "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"asset_type":    "credit_alphanum4",
+				"clear_flags":   []int32{1, 2},
+				"clear_flags_s": []string{"authorized", "authorized_to_maintain_liabilities"},
+				"set_flags":     []int32{4},
+				"set_flags_s":   []string{"clawback_enabled"},
+				"trustor":       "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"}},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"liquidity_pool_id":        "0102030405060708090000000000000000000000000000000000000000000000",
+				"max_price":                1e+06,
+				"max_price_r":              Price{Numerator: 1000000, Denominator: 1},
+				"min_price":                1e-06,
+				"min_price_r":              Price{Numerator: 1, Denominator: 1000000},
+				"reserve_a_asset_code":     "USDT",
+				"reserve_a_asset_id":       int64(-8205667356306085451),
+				"reserve_a_asset_issuer":   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"reserve_a_asset_type":     "credit_alphanum4",
+				"reserve_a_deposit_amount": 1e-07,
+				"reserve_a_max_amount":     0.0001,
+				"reserve_b_asset_code":     "USDT",
+				"reserve_b_asset_id":       int64(-8205667356306085451),
+				"reserve_b_asset_issuer":   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"reserve_b_asset_type":     "credit_alphanum4",
+				"reserve_b_deposit_amount": 1e-07,
+				"reserve_b_max_amount":     1e-05,
+				"shares_received":          1e-07,
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"liquidity_pool_id":         "0102030405060708090000000000000000000000000000000000000000000000",
+				"reserve_a_asset_code":      "USDT",
+				"reserve_a_asset_id":        int64(-8205667356306085451),
+				"reserve_a_asset_issuer":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"reserve_a_asset_type":      "credit_alphanum4",
+				"reserve_a_min_amount":      1e-07,
+				"reserve_a_withdraw_amount": -1e-07,
+				"reserve_b_asset_code":      "USDT",
+				"reserve_b_asset_id":        int64(-8205667356306085451),
+				"reserve_b_asset_issuer":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"reserve_b_asset_type":      "credit_alphanum4",
+				"reserve_b_min_amount":      1e-07,
+				"reserve_b_withdraw_amount": -1e-07,
+				"shares":                    4e-07,
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"asset_balance_changes": []map[string]interface{}{},
+				"contract_id":           "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37",
+				"function":              "HostFunctionTypeHostFunctionTypeInvokeContract",
+				"ledger_key_hash":       []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				"parameters": []map[string]string{
+					{
+						"type":  "Address",
+						"value": "AAAAEgAAAAESNFZ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
+					},
+					{
+						"type":  "Sym",
+						"value": "AAAADwAAAAR0ZXN0",
+					},
+				},
+				"parameters_decoded": []map[string]string{
+					{
+						"type":  "Address",
+						"value": "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37",
+					},
+					{
+						"type":  "Sym",
+						"value": "test",
+					},
+				},
+				"type": "invoke_contract",
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"address":         "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37",
+				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
+				"from":            "address",
+				"function":        "HostFunctionTypeHostFunctionTypeCreateContract",
+				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				"type":            "create_contract",
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"asset":           "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
+				"from":            "asset",
+				"function":        "HostFunctionTypeHostFunctionTypeCreateContract",
+				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				"type":            "create_contract",
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"asset":           "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
+				"from":            "asset",
+				"function":        "HostFunctionTypeHostFunctionTypeCreateContractV2",
+				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				"parameters": []map[string]string{
+					{
+						"type":  "B",
+						"value": "AAAAAAAAAAE=",
+					},
+				},
+				"parameters_decoded": []map[string]string{
+					{
+						"type":  "B",
+						"value": "true",
+					},
+				},
+				"type": "create_contract_v2",
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"function":        "HostFunctionTypeHostFunctionTypeUploadContractWasm",
+				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				"type":            "upload_wasm",
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"extend_to":       int32(1234),
+				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
+				"type":            "extend_footprint_ttl",
+			},
+		},
+		{
+			err: nil,
+			result: map[string]interface{}{
+				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
+				"type":            "restore_footprint",
+			},
+		},
+	}
+
+	return output
+}
diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go
index 36f82b952f..8490163699 100644
--- a/ingest/ledger_transaction.go
+++ b/ingest/ledger_transaction.go
@@ -579,3 +579,163 @@ func (t *LedgerTransaction) NewMaxFee() (uint32, bool) {
 func (t *LedgerTransaction) Successful() bool {
 	return t.Result.Successful()
 }
+
+func (t *LedgerTransaction) GetOperations(networkPassphrase string) []LedgerOperation {
+	var ledgerOperations []LedgerOperation
+
+	for i, operation := range t.Envelope.Operations() {
+		ledgerOperation := LedgerOperation{
+			OperationIndex:    int32(i),
+			Operation:         operation,
+			Transaction:       t,
+			NetworkPassphrase: networkPassphrase,
+		}
+		ledgerOperations = append(ledgerOperations, ledgerOperation)
+	}
+
+	return ledgerOperations
+}
+
+func (t *LedgerTransaction) GetTransactionV1Envelope() (xdr.TransactionV1Envelope, bool) {
+	switch t.Envelope.Type {
+	case xdr.EnvelopeTypeEnvelopeTypeTx:
+		switch t.Envelope.Type {
+		case 2:
+			return *t.Envelope.V1, true
+		default:
+			return xdr.TransactionV1Envelope{}, false
+		}
+	case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump:
+		return t.Envelope.MustFeeBump().Tx.InnerTx.MustV1(), true
+	default:
+		return xdr.TransactionV1Envelope{}, false
+	}
+}
+
+func (t *LedgerTransaction) LedgerKeyHashFromTxEnvelope() []string {
+	var ledgerKeyHash []string
+
+	v1Envelope, ok := t.GetTransactionV1Envelope()
+	if !ok {
+		return ledgerKeyHash
+	}
+
+	for _, ledgerKey := range v1Envelope.Tx.Ext.SorobanData.Resources.Footprint.ReadOnly {
+		ledgerKeyBase64, err := ledgerKey.MarshalBinaryBase64()
+		if err != nil {
+			panic(err)
+		}
+		if ledgerKeyBase64 != "" {
+			ledgerKeyHash = append(ledgerKeyHash, ledgerKeyBase64)
+		}
+	}
+
+	for _, ledgerKey := range v1Envelope.Tx.Ext.SorobanData.Resources.Footprint.ReadWrite {
+		ledgerKeyBase64, err := ledgerKey.MarshalBinaryBase64()
+		if err != nil {
+			panic(err)
+		}
+		if ledgerKeyBase64 != "" {
+			ledgerKeyHash = append(ledgerKeyHash, ledgerKeyBase64)
+		}
+	}
+
+	return ledgerKeyHash
+}
+
+func (t *LedgerTransaction) ContractCodeHashFromTxEnvelope() (string, bool) {
+	v1Envelope, ok := t.GetTransactionV1Envelope()
+	if !ok {
+		return "", false
+	}
+	for _, ledgerKey := range v1Envelope.Tx.Ext.SorobanData.Resources.Footprint.ReadOnly {
+		contractCode, ok := t.contractCodeFromContractData(ledgerKey)
+		if !ok {
+			return "", false
+		}
+		if contractCode != "" {
+			return contractCode, true
+		}
+	}
+
+	for _, ledgerKey := range v1Envelope.Tx.Ext.SorobanData.Resources.Footprint.ReadWrite {
+		contractCode, ok := t.contractCodeFromContractData(ledgerKey)
+		if !ok {
+			return "", false
+		}
+		if contractCode != "" {
+			return contractCode, true
+		}
+	}
+
+	return "", true
+}
+
+func (t *LedgerTransaction) contractCodeFromContractData(ledgerKey xdr.LedgerKey) (string, bool) {
+	contractCode, ok := ledgerKey.GetContractCode()
+	if !ok {
+		return "", false
+	}
+
+	codeHash, err := contractCode.Hash.MarshalBinaryBase64()
+	if err != nil {
+		panic(err)
+	}
+
+	return codeHash, true
+}
+
+func (t *LedgerTransaction) contractIdFromTxEnvelope() (string, bool) {
+	v1Envelope, ok := t.GetTransactionV1Envelope()
+	if !ok {
+		return "", false
+	}
+	for _, ledgerKey := range v1Envelope.Tx.Ext.SorobanData.Resources.Footprint.ReadWrite {
+		contractId, ok := t.contractIdFromContractData(ledgerKey)
+		if !ok {
+			return "", false
+		}
+
+		if contractId != "" {
+			return contractId, true
+		}
+	}
+
+	for _, ledgerKey := range v1Envelope.Tx.Ext.SorobanData.Resources.Footprint.ReadOnly {
+		contractId, ok := t.contractIdFromContractData(ledgerKey)
+		if !ok {
+			return "", false
+		}
+
+		if contractId != "" {
+			return contractId, true
+		}
+	}
+
+	return "", true
+}
+
+func (t *LedgerTransaction) contractIdFromContractData(ledgerKey xdr.LedgerKey) (string, bool) {
+	contractData, ok := ledgerKey.GetContractData()
+	if !ok {
+		return "", false
+	}
+	contractIdHash, ok := contractData.Contract.GetContractId()
+	if !ok {
+		return "", false
+	}
+
+	var contractIdByte []byte
+	var contractId string
+	var err error
+	contractIdByte, err = contractIdHash.MarshalBinary()
+	if err != nil {
+		panic(err)
+	}
+	contractId, err = strkey.Encode(strkey.VersionByteContract, contractIdByte)
+	if err != nil {
+		panic(err)
+	}
+
+	return contractId, true
+}
diff --git a/xdr/hash.go b/xdr/hash.go
index 2a15c18c9c..c0107163b6 100644
--- a/xdr/hash.go
+++ b/xdr/hash.go
@@ -1,6 +1,9 @@
 package xdr
 
-import "encoding/hex"
+import (
+	"encoding/base64"
+	"encoding/hex"
+)
 
 func (h Hash) HexString() string {
 	return hex.EncodeToString(h[:])
@@ -17,3 +20,14 @@ func (s Hash) Equals(o Hash) bool {
 	}
 	return true
 }
+
+// MarshalBinaryBase64 marshals XDR into a binary form and then encodes it
+// using base64.
+func (h Hash) MarshalBinaryBase64() (string, error) {
+	b, err := h.MarshalBinary()
+	if err != nil {
+		return "", err
+	}
+
+	return base64.StdEncoding.EncodeToString(b), nil
+}
diff --git a/xdr/operation_trace_result.go b/xdr/operation_trace_result.go
new file mode 100644
index 0000000000..f5c68c7989
--- /dev/null
+++ b/xdr/operation_trace_result.go
@@ -0,0 +1,68 @@
+package xdr
+
+import "fmt"
+
+func (o OperationResultTr) MapOperationResultTr() (string, error) {
+	var operationTraceDescription string
+	operationType := o.Type
+
+	switch operationType {
+	case OperationTypeCreateAccount:
+		operationTraceDescription = o.CreateAccountResult.Code.String()
+	case OperationTypePayment:
+		operationTraceDescription = o.PaymentResult.Code.String()
+	case OperationTypePathPaymentStrictReceive:
+		operationTraceDescription = o.PathPaymentStrictReceiveResult.Code.String()
+	case OperationTypePathPaymentStrictSend:
+		operationTraceDescription = o.PathPaymentStrictSendResult.Code.String()
+	case OperationTypeManageBuyOffer:
+		operationTraceDescription = o.ManageBuyOfferResult.Code.String()
+	case OperationTypeManageSellOffer:
+		operationTraceDescription = o.ManageSellOfferResult.Code.String()
+	case OperationTypeCreatePassiveSellOffer:
+		operationTraceDescription = o.CreatePassiveSellOfferResult.Code.String()
+	case OperationTypeSetOptions:
+		operationTraceDescription = o.SetOptionsResult.Code.String()
+	case OperationTypeChangeTrust:
+		operationTraceDescription = o.ChangeTrustResult.Code.String()
+	case OperationTypeAllowTrust:
+		operationTraceDescription = o.AllowTrustResult.Code.String()
+	case OperationTypeAccountMerge:
+		operationTraceDescription = o.AccountMergeResult.Code.String()
+	case OperationTypeInflation:
+		operationTraceDescription = o.InflationResult.Code.String()
+	case OperationTypeManageData:
+		operationTraceDescription = o.ManageDataResult.Code.String()
+	case OperationTypeBumpSequence:
+		operationTraceDescription = o.BumpSeqResult.Code.String()
+	case OperationTypeCreateClaimableBalance:
+		operationTraceDescription = o.CreateClaimableBalanceResult.Code.String()
+	case OperationTypeClaimClaimableBalance:
+		operationTraceDescription = o.ClaimClaimableBalanceResult.Code.String()
+	case OperationTypeBeginSponsoringFutureReserves:
+		operationTraceDescription = o.BeginSponsoringFutureReservesResult.Code.String()
+	case OperationTypeEndSponsoringFutureReserves:
+		operationTraceDescription = o.EndSponsoringFutureReservesResult.Code.String()
+	case OperationTypeRevokeSponsorship:
+		operationTraceDescription = o.RevokeSponsorshipResult.Code.String()
+	case OperationTypeClawback:
+		operationTraceDescription = o.ClawbackResult.Code.String()
+	case OperationTypeClawbackClaimableBalance:
+		operationTraceDescription = o.ClawbackClaimableBalanceResult.Code.String()
+	case OperationTypeSetTrustLineFlags:
+		operationTraceDescription = o.SetTrustLineFlagsResult.Code.String()
+	case OperationTypeLiquidityPoolDeposit:
+		operationTraceDescription = o.LiquidityPoolDepositResult.Code.String()
+	case OperationTypeLiquidityPoolWithdraw:
+		operationTraceDescription = o.LiquidityPoolWithdrawResult.Code.String()
+	case OperationTypeInvokeHostFunction:
+		operationTraceDescription = o.InvokeHostFunctionResult.Code.String()
+	case OperationTypeExtendFootprintTtl:
+		operationTraceDescription = o.ExtendFootprintTtlResult.Code.String()
+	case OperationTypeRestoreFootprint:
+		operationTraceDescription = o.RestoreFootprintResult.Code.String()
+	default:
+		return operationTraceDescription, fmt.Errorf("unknown operation type: %s", o.Type.String())
+	}
+	return operationTraceDescription, nil
+}

From 3bc82f44da9be4d41bf74f14fc2fa8a78f9925e9 Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Tue, 21 Jan 2025 13:50:01 -0500
Subject: [PATCH 02/12] update tests

---
 ingest/ledger_operation_test.go | 133 ++++++++++++++++++++++++--------
 1 file changed, 102 insertions(+), 31 deletions(-)

diff --git a/ingest/ledger_operation_test.go b/ingest/ledger_operation_test.go
index 18c4760219..83978ee7fa 100644
--- a/ingest/ledger_operation_test.go
+++ b/ingest/ledger_operation_test.go
@@ -8,6 +8,34 @@ import (
 )
 
 func TestOperation(t *testing.T) {
+	o := LedgerOperation{
+		OperationIndex:    int32(0),
+		Operation:         operationTestInput()[1],
+		Transaction:       transactionTestInput(),
+		NetworkPassphrase: "",
+	}
+
+	assert.Equal(t, "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", o.SourceAccount())
+	assert.Equal(t, int32(1), o.Type())
+	assert.Equal(t, "OperationTypePayment", o.TypeString())
+	assert.Equal(t, int64(131335723340009473), o.ID())
+
+	var ok bool
+	var sourceAccountMuxed string
+	sourceAccountMuxed, ok = o.SourceAccountMuxed()
+	assert.Equal(t, true, ok)
+	assert.Equal(t, "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", sourceAccountMuxed)
+
+	assert.Equal(t, "OperationResultCodeOpInner", o.OperationResultCode())
+
+	var err error
+	var operationTraceCode string
+	operationTraceCode, err = o.OperationTraceCode()
+	assert.Equal(t, nil, err)
+	assert.Equal(t, "PathPaymentStrictReceiveResultCodePathPaymentStrictReceiveSuccess", operationTraceCode)
+}
+
+func TestOperationDetails(t *testing.T) {
 	testOutput := resultTestOutput()
 	for i, op := range operationTestInput() {
 		ledgerOperation := LedgerOperation{
@@ -143,7 +171,17 @@ func transactionTestInput() *LedgerTransaction {
 				Result: xdr.TransactionResultResult{
 					Code: 0,
 					Results: &[]xdr.OperationResult{
-						{},
+						{
+							Code: 0,
+							Tr: &xdr.OperationResultTr{
+								Type: 2,
+								PathPaymentStrictReceiveResult: &xdr.PathPaymentStrictReceiveResult{
+									Code:     0,
+									Success:  &xdr.PathPaymentStrictReceiveResultSuccess{},
+									NoIssuer: &xdr.Asset{},
+								},
+							},
+						},
 						{},
 						{},
 						{
@@ -1086,9 +1124,13 @@ func resultTestOutput() []testOutput {
 				"asset_id":   int64(-5706705804583548011),
 				"asset_type": "native",
 				"from":       "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN",
-				"path": []Path{Path{AssetCode: "USDT",
-					AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-					AssetType:   "credit_alphanum4"}},
+				"path": []Path{
+					{
+						AssetCode:   "USDT",
+						AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+						AssetType:   "credit_alphanum4",
+					},
+				},
 				"source_amount":     float64(0),
 				"source_asset_id":   int64(-5706705804583548011),
 				"source_asset_type": "native",
@@ -1103,8 +1145,10 @@ func resultTestOutput() []testOutput {
 				"buying_asset_type": "native",
 				"offer_id":          int64(0),
 				"price":             0.514092,
-				"price_r": Price{Numerator: 128523,
-					Denominator: 250000},
+				"price_r": Price{
+					Numerator:   128523,
+					Denominator: 250000,
+				},
 				"selling_asset_code":   "USDT",
 				"selling_asset_id":     int64(-8205667356306085451),
 				"selling_asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
@@ -1119,8 +1163,10 @@ func resultTestOutput() []testOutput {
 				"buying_asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 				"buying_asset_type":   "credit_alphanum4",
 				"price":               0.0791606,
-				"price_r": Price{Numerator: 99583200,
-					Denominator: 1257990000},
+				"price_r": Price{
+					Numerator:   99583200,
+					Denominator: 1257990000,
+				},
 				"selling_asset_id":   int64(-5706705804583548011),
 				"selling_asset_type": "native"},
 		},
@@ -1211,8 +1257,10 @@ func resultTestOutput() []testOutput {
 				"buying_asset_type": "native",
 				"offer_id":          int64(100),
 				"price":             0.3496823,
-				"price_r": Price{Numerator: 635863285,
-					Denominator: 1818402817},
+				"price_r": Price{
+					Numerator:   635863285,
+					Denominator: 1818402817,
+				},
 				"selling_asset_code":   "USDT",
 				"selling_asset_id":     int64(-8205667356306085451),
 				"selling_asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
@@ -1228,9 +1276,13 @@ func resultTestOutput() []testOutput {
 				"from":            "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
 				"from_muxed":      "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
 				"from_muxed_id":   uint64(123),
-				"path": []Path{{AssetCode: "USDT",
-					AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-					AssetType:   "credit_alphanum4"}},
+				"path": []Path{
+					{
+						AssetCode:   "USDT",
+						AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+						AssetType:   "credit_alphanum4",
+					},
+				},
 				"source_amount":     0.1598182,
 				"source_asset_id":   int64(-5706705804583548011),
 				"source_asset_type": "native",
@@ -1241,19 +1293,27 @@ func resultTestOutput() []testOutput {
 			result: map[string]interface{}{
 				"amount": 123456.789,
 				"asset":  "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"claimants": []Claimant{{Destination: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-					Predicate: xdr.ClaimPredicate{Type: 0,
-						AndPredicates: (*[]xdr.ClaimPredicate)(nil),
-						OrPredicates:  (*[]xdr.ClaimPredicate)(nil),
-						NotPredicate:  (**xdr.ClaimPredicate)(nil),
-						AbsBefore:     (*xdr.Int64)(nil),
-						RelBefore:     (*xdr.Int64)(nil)}}}},
+				"claimants": []Claimant{
+					{
+						Destination: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+						Predicate: xdr.ClaimPredicate{
+							Type:          0,
+							AndPredicates: (*[]xdr.ClaimPredicate)(nil),
+							OrPredicates:  (*[]xdr.ClaimPredicate)(nil),
+							NotPredicate:  (**xdr.ClaimPredicate)(nil),
+							AbsBefore:     (*xdr.Int64)(nil),
+							RelBefore:     (*xdr.Int64)(nil),
+						},
+					},
+				},
+			},
 		},
 		{
 			err: nil,
 			result: map[string]interface{}{
 				"balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000",
-				"claimant":   "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN"},
+				"claimant":   "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN",
+			},
 		},
 		{
 			err: nil,
@@ -1265,39 +1325,46 @@ func resultTestOutput() []testOutput {
 			err: nil,
 			result: map[string]interface{}{
 				"signer_account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"signer_key":        "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"},
+				"signer_key":        "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
+			},
 		},
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+				"account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+			},
 		},
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"claimable_balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000"},
+				"claimable_balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000",
+			},
 		},
 		{
 			err: nil,
 			result: map[string]interface{}{
 				"data_account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"data_name":       "test"},
+				"data_name":       "test",
+			},
 		},
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"offer_id": int64(100)},
+				"offer_id": int64(100),
+			},
 		},
 		{
 			err: nil,
 			result: map[string]interface{}{
 				"trustline_account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"trustline_asset":      "USTT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+				"trustline_asset":      "USTT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+			},
 		},
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"liquidity_pool_id": "0102030405060708090000000000000000000000000000000000000000000000"},
+				"liquidity_pool_id": "0102030405060708090000000000000000000000000000000000000000000000",
+			},
 		},
 		{
 			err: nil,
@@ -1307,12 +1374,14 @@ func resultTestOutput() []testOutput {
 				"asset_id":     int64(-8205667356306085451),
 				"asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 				"asset_type":   "credit_alphanum4",
-				"from":         "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+				"from":         "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+			},
 		},
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000"},
+				"balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000",
+			},
 		},
 		{
 			err: nil,
@@ -1325,7 +1394,9 @@ func resultTestOutput() []testOutput {
 				"clear_flags_s": []string{"authorized", "authorized_to_maintain_liabilities"},
 				"set_flags":     []int32{4},
 				"set_flags_s":   []string{"clawback_enabled"},
-				"trustor":       "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"}},
+				"trustor":       "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+			},
+		},
 		{
 			err: nil,
 			result: map[string]interface{}{

From c68cd9a766a4b492d6469ee3c119e2935eb72818 Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Thu, 23 Jan 2025 13:37:20 -0500
Subject: [PATCH 03/12] updates from comments

---
 ingest/ledger_operation.go      | 150 ++++++++++++++++----------------
 ingest/ledger_operation_test.go |  58 +++++++-----
 2 files changed, 112 insertions(+), 96 deletions(-)

diff --git a/ingest/ledger_operation.go b/ingest/ledger_operation.go
index f59add11e2..cf7f29f799 100644
--- a/ingest/ledger_operation.go
+++ b/ingest/ledger_operation.go
@@ -71,10 +71,13 @@ func (o *LedgerOperation) OperationResultCode() string {
 
 func (o *LedgerOperation) OperationTraceCode() (string, error) {
 	var operationTraceCode string
+	var operationResults []xdr.OperationResult
+	var ok bool
 
-	operationResults, ok := o.Transaction.Result.Result.OperationResults()
+	operationResults, ok = o.Transaction.Result.Result.OperationResults()
 	if ok {
-		operationResultTr, ok := operationResults[o.OperationIndex].GetTr()
+		var operationResultTr xdr.OperationResultTr
+		operationResultTr, ok = operationResults[o.OperationIndex].GetTr()
 		if ok {
 			operationTraceCode, err := operationResultTr.MapOperationResultTr()
 			if err != nil {
@@ -245,7 +248,7 @@ func (o *LedgerOperation) CreateAccountDetails() (map[string]interface{}, error)
 		return details, err
 	}
 	details["account"] = op.Destination.Address()
-	details["starting_balance"] = o.ConvertStroopValueToReal(op.StartingBalance)
+	details["starting_balance"] = int64(op.StartingBalance)
 
 	return details, nil
 }
@@ -282,36 +285,14 @@ func (o *LedgerOperation) PaymentDetails() (map[string]interface{}, error) {
 	if err := o.addAccountAndMuxedAccountDetails(details, op.Destination, "to"); err != nil {
 		return details, err
 	}
-	details["amount"] = o.ConvertStroopValueToReal(op.Amount)
-	if err := o.addAssetDetailsToOperationDetails(details, op.Asset, ""); err != nil {
+	details["amount"] = int64(op.Amount)
+	if err := o.addAssetDetails(details, op.Asset, ""); err != nil {
 		return details, err
 	}
 
 	return details, nil
 }
 
-func (o *LedgerOperation) addAssetDetailsToOperationDetails(result map[string]interface{}, asset xdr.Asset, prefix string) error {
-	var assetType, code, issuer string
-	err := asset.Extract(&assetType, &code, &issuer)
-	if err != nil {
-		return err
-	}
-
-	prefix = o.FormatPrefix(prefix)
-	result[prefix+"asset_type"] = assetType
-
-	if asset.Type == xdr.AssetTypeAssetTypeNative {
-		result[prefix+"asset_id"] = int64(-5706705804583548011)
-		return nil
-	}
-
-	result[prefix+"asset_code"] = code
-	result[prefix+"asset_issuer"] = issuer
-	result[prefix+"asset_id"] = o.FarmHashAsset(code, issuer, assetType)
-
-	return nil
-}
-
 func (o *LedgerOperation) PathPaymentStrictReceiveDetails() (map[string]interface{}, error) {
 	details := map[string]interface{}{}
 	op, ok := o.Operation.Body.GetPathPaymentStrictReceiveOp()
@@ -325,13 +306,13 @@ func (o *LedgerOperation) PathPaymentStrictReceiveDetails() (map[string]interfac
 	if err := o.addAccountAndMuxedAccountDetails(details, op.Destination, "to"); err != nil {
 		return details, err
 	}
-	details["amount"] = o.ConvertStroopValueToReal(op.DestAmount)
+	details["amount"] = int64(op.DestAmount)
 	details["source_amount"] = amount.String(0)
-	details["source_max"] = o.ConvertStroopValueToReal(op.SendMax)
-	if err := o.addAssetDetailsToOperationDetails(details, op.DestAsset, ""); err != nil {
+	details["source_max"] = int64(op.SendMax)
+	if err := o.addAssetDetails(details, op.DestAsset, ""); err != nil {
 		return details, err
 	}
-	if err := o.addAssetDetailsToOperationDetails(details, op.SendAsset, "source"); err != nil {
+	if err := o.addAssetDetails(details, op.SendAsset, "source"); err != nil {
 		return details, err
 	}
 
@@ -349,7 +330,7 @@ func (o *LedgerOperation) PathPaymentStrictReceiveDetails() (map[string]interfac
 		if !ok {
 			return details, fmt.Errorf("could not access PathPaymentStrictReceive result info for this operation (index %d)", o.OperationIndex)
 		}
-		details["source_amount"] = o.ConvertStroopValueToReal(result.SendAmount())
+		details["source_amount"] = int64(result.SendAmount())
 	}
 
 	details["path"] = o.TransformPath(op.Path)
@@ -370,12 +351,12 @@ func (o *LedgerOperation) PathPaymentStrictSendDetails() (map[string]interface{}
 		return details, err
 	}
 	details["amount"] = float64(0)
-	details["source_amount"] = o.ConvertStroopValueToReal(op.SendAmount)
+	details["source_amount"] = int64(op.SendAmount)
 	details["destination_min"] = amount.String(op.DestMin)
-	if err := o.addAssetDetailsToOperationDetails(details, op.DestAsset, ""); err != nil {
+	if err := o.addAssetDetails(details, op.DestAsset, ""); err != nil {
 		return details, err
 	}
-	if err := o.addAssetDetailsToOperationDetails(details, op.SendAsset, "source"); err != nil {
+	if err := o.addAssetDetails(details, op.SendAsset, "source"); err != nil {
 		return details, err
 	}
 
@@ -393,7 +374,7 @@ func (o *LedgerOperation) PathPaymentStrictSendDetails() (map[string]interface{}
 		if !ok {
 			return details, fmt.Errorf("could not access GetPathPaymentStrictSendResult result info for this operation (index %d)", o.OperationIndex)
 		}
-		details["amount"] = o.ConvertStroopValueToReal(result.DestAmount())
+		details["amount"] = int64(result.DestAmount())
 	}
 
 	details["path"] = o.TransformPath(op.Path)
@@ -408,15 +389,15 @@ func (o *LedgerOperation) ManageBuyOfferDetails() (map[string]interface{}, error
 	}
 
 	details["offer_id"] = int64(op.OfferId)
-	details["amount"] = o.ConvertStroopValueToReal(op.BuyAmount)
+	details["amount"] = int64(op.BuyAmount)
 	if err := o.addPriceDetails(details, op.Price, ""); err != nil {
 		return details, err
 	}
 
-	if err := o.addAssetDetailsToOperationDetails(details, op.Buying, "buying"); err != nil {
+	if err := o.addAssetDetails(details, op.Buying, "buying"); err != nil {
 		return details, err
 	}
-	if err := o.addAssetDetailsToOperationDetails(details, op.Selling, "selling"); err != nil {
+	if err := o.addAssetDetails(details, op.Selling, "selling"); err != nil {
 		return details, err
 	}
 
@@ -445,15 +426,15 @@ func (o *LedgerOperation) ManageSellOfferDetails() (map[string]interface{}, erro
 	}
 
 	details["offer_id"] = int64(op.OfferId)
-	details["amount"] = o.ConvertStroopValueToReal(op.Amount)
+	details["amount"] = int64(op.Amount)
 	if err := o.addPriceDetails(details, op.Price, ""); err != nil {
 		return details, err
 	}
 
-	if err := o.addAssetDetailsToOperationDetails(details, op.Buying, "buying"); err != nil {
+	if err := o.addAssetDetails(details, op.Buying, "buying"); err != nil {
 		return details, err
 	}
-	if err := o.addAssetDetailsToOperationDetails(details, op.Selling, "selling"); err != nil {
+	if err := o.addAssetDetails(details, op.Selling, "selling"); err != nil {
 		return details, err
 	}
 
@@ -466,15 +447,15 @@ func (o *LedgerOperation) CreatePassiveSellOfferDetails() (map[string]interface{
 		return details, fmt.Errorf("could not access CreatePassiveSellOffer info for this operation (index %d)", o.OperationIndex)
 	}
 
-	details["amount"] = o.ConvertStroopValueToReal(op.Amount)
+	details["amount"] = int64(op.Amount)
 	if err := o.addPriceDetails(details, op.Price, ""); err != nil {
 		return details, err
 	}
 
-	if err := o.addAssetDetailsToOperationDetails(details, op.Buying, "buying"); err != nil {
+	if err := o.addAssetDetails(details, op.Buying, "buying"); err != nil {
 		return details, err
 	}
-	if err := o.addAssetDetailsToOperationDetails(details, op.Selling, "selling"); err != nil {
+	if err := o.addAssetDetails(details, op.Selling, "selling"); err != nil {
 		return details, err
 	}
 
@@ -568,7 +549,7 @@ func (o *LedgerOperation) ChangeTrustDetails() (map[string]interface{}, error) {
 			return details, err
 		}
 	} else {
-		if err := o.addAssetDetailsToOperationDetails(details, op.Line.ToAsset(), ""); err != nil {
+		if err := o.addAssetDetails(details, op.Line.ToAsset(), ""); err != nil {
 			return details, err
 		}
 		details["trustee"] = details["asset_issuer"]
@@ -578,7 +559,7 @@ func (o *LedgerOperation) ChangeTrustDetails() (map[string]interface{}, error) {
 		return details, err
 	}
 
-	details["limit"] = o.ConvertStroopValueToReal(op.Limit)
+	details["limit"] = int64(op.Limit)
 
 	return details, nil
 }
@@ -606,7 +587,7 @@ func (o *LedgerOperation) AllowTrustDetails() (map[string]interface{}, error) {
 		return details, fmt.Errorf("could not access AllowTrust info for this operation (index %d)", o.OperationIndex)
 	}
 
-	if err := o.addAssetDetailsToOperationDetails(details, op.Asset.ToAsset(o.sourceAccountXDR().ToAccountId()), ""); err != nil {
+	if err := o.addAssetDetails(details, op.Asset.ToAsset(o.sourceAccountXDR().ToAccountId()), ""); err != nil {
 		return details, err
 	}
 	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "trustee"); err != nil {
@@ -686,7 +667,12 @@ func (o *LedgerOperation) CreateClaimableBalanceDetails() (map[string]interface{
 	}
 
 	details["asset"] = op.Asset.StringCanonical()
-	details["amount"] = o.ConvertStroopValueToReal(op.Amount)
+	err := o.addAssetDetails(details, op.Asset, "")
+	if err != nil {
+		return details, err
+	}
+
+	details["amount"] = int64(op.Amount)
 	details["claimants"] = o.TransformClaimants(op.Claimants)
 
 	return details, nil
@@ -813,13 +799,13 @@ func (o *LedgerOperation) ClawbackDetails() (map[string]interface{}, error) {
 		return details, fmt.Errorf("could not access Clawback info for this operation (index %d)", o.OperationIndex)
 	}
 
-	if err := o.addAssetDetailsToOperationDetails(details, op.Asset, ""); err != nil {
+	if err := o.addAssetDetails(details, op.Asset, ""); err != nil {
 		return details, err
 	}
 	if err := o.addAccountAndMuxedAccountDetails(details, op.From, "from"); err != nil {
 		return details, err
 	}
-	details["amount"] = o.ConvertStroopValueToReal(op.Amount)
+	details["amount"] = int64(op.Amount)
 
 	return details, nil
 }
@@ -846,7 +832,7 @@ func (o *LedgerOperation) SetTrustLineFlagsDetails() (map[string]interface{}, er
 	}
 
 	details["trustor"] = op.Trustor.Address()
-	if err := o.addAssetDetailsToOperationDetails(details, op.Asset, ""); err != nil {
+	if err := o.addAssetDetails(details, op.Asset, ""); err != nil {
 		return details, err
 	}
 	if op.SetFlags > 0 {
@@ -912,10 +898,10 @@ func (o *LedgerOperation) LiquidityPoolDepositDetails() (map[string]interface{},
 	}
 
 	// Process ReserveA Details
-	if err := o.addAssetDetailsToOperationDetails(details, assetA, "reserve_a"); err != nil {
+	if err := o.addAssetDetails(details, assetA, "reserve_a"); err != nil {
 		return details, err
 	}
-	details["reserve_a_max_amount"] = o.ConvertStroopValueToReal(op.MaxAmountA)
+	details["reserve_a_max_amount"] = int64(op.MaxAmountA)
 	depositA, err := strconv.ParseFloat(amount.String(depositedA), 64)
 	if err != nil {
 		return details, err
@@ -923,10 +909,10 @@ func (o *LedgerOperation) LiquidityPoolDepositDetails() (map[string]interface{},
 	details["reserve_a_deposit_amount"] = depositA
 
 	//Process ReserveB Details
-	if err := o.addAssetDetailsToOperationDetails(details, assetB, "reserve_b"); err != nil {
+	if err := o.addAssetDetails(details, assetB, "reserve_b"); err != nil {
 		return details, err
 	}
-	details["reserve_b_max_amount"] = o.ConvertStroopValueToReal(op.MaxAmountB)
+	details["reserve_b_max_amount"] = int64(op.MaxAmountB)
 	depositB, err := strconv.ParseFloat(amount.String(depositedB), 64)
 	if err != nil {
 		return details, err
@@ -1022,20 +1008,20 @@ func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (map[string]interface{}
 		receivedA, receivedB = -delta.ReserveA, -delta.ReserveB
 	}
 	// Process AssetA Details
-	if err := o.addAssetDetailsToOperationDetails(details, assetA, "reserve_a"); err != nil {
+	if err := o.addAssetDetails(details, assetA, "reserve_a"); err != nil {
 		return details, err
 	}
-	details["reserve_a_min_amount"] = o.ConvertStroopValueToReal(op.MinAmountA)
-	details["reserve_a_withdraw_amount"] = o.ConvertStroopValueToReal(receivedA)
+	details["reserve_a_min_amount"] = int64(op.MinAmountA)
+	details["reserve_a_withdraw_amount"] = int64(receivedA)
 
 	// Process AssetB Details
-	if err := o.addAssetDetailsToOperationDetails(details, assetB, "reserve_b"); err != nil {
+	if err := o.addAssetDetails(details, assetB, "reserve_b"); err != nil {
 		return details, err
 	}
-	details["reserve_b_min_amount"] = o.ConvertStroopValueToReal(op.MinAmountB)
-	details["reserve_b_withdraw_amount"] = o.ConvertStroopValueToReal(receivedB)
+	details["reserve_b_min_amount"] = int64(op.MinAmountB)
+	details["reserve_b_withdraw_amount"] = int64(receivedB)
 
-	details["shares"] = o.ConvertStroopValueToReal(op.Amount)
+	details["shares"] = int64(op.Amount)
 
 	return details, nil
 }
@@ -1074,12 +1060,13 @@ func (o *LedgerOperation) InvokeHostFunctionDetails() (map[string]interface{}, e
 
 		details["parameters"], details["parameters_decoded"] = o.serializeParameters(args)
 
-		if balanceChanges, err := o.parseAssetBalanceChangesFromContractEvents(); err != nil {
+		balanceChanges, err := o.parseAssetBalanceChangesFromContractEvents()
+		if err != nil {
 			return nil, err
-		} else {
-			details["asset_balance_changes"] = balanceChanges
 		}
 
+		details["asset_balance_changes"] = balanceChanges
+
 	case xdr.HostFunctionTypeHostFunctionTypeCreateContract:
 		args := op.HostFunction.MustCreateContract()
 		details["type"] = "create_contract"
@@ -1098,7 +1085,11 @@ func (o *LedgerOperation) InvokeHostFunctionDetails() (map[string]interface{}, e
 			details["contract_code_hash"] = contractCodeHash
 		}
 
-		preimageTypeMap := o.switchContractIdPreimageType(args.ContractIdPreimage)
+		preimageTypeMap, err := o.switchContractIdPreimageType(args.ContractIdPreimage)
+		if err != nil {
+			return details, nil
+		}
+
 		for key, val := range preimageTypeMap {
 			if _, ok := preimageTypeMap[key]; ok {
 				details[key] = val
@@ -1137,7 +1128,11 @@ func (o *LedgerOperation) InvokeHostFunctionDetails() (map[string]interface{}, e
 		constructorArgs := args.ConstructorArgs
 		details["parameters"], details["parameters_decoded"] = o.serializeParameters(constructorArgs)
 
-		preimageTypeMap := o.switchContractIdPreimageType(args.ContractIdPreimage)
+		preimageTypeMap, err := o.switchContractIdPreimageType(args.ContractIdPreimage)
+		if err != nil {
+			return details, nil
+		}
+
 		for key, val := range preimageTypeMap {
 			if _, ok := preimageTypeMap[key]; ok {
 				details[key] = val
@@ -1180,7 +1175,7 @@ func (o *LedgerOperation) RestoreFootprintDetails() (map[string]interface{}, err
 	details := map[string]interface{}{}
 	_, ok := o.Operation.Body.GetRestoreFootprintOp()
 	if !ok {
-		return details, fmt.Errorf("could not access InvokeHostFunction info for this operation (index %d)", o.OperationIndex)
+		return details, fmt.Errorf("could not access RestoreFootprint info for this operation (index %d)", o.OperationIndex)
 	}
 
 	details["type"] = "restore_footprint"
@@ -1311,18 +1306,23 @@ func (o *LedgerOperation) addAssetDetails(result map[string]interface{}, a xdr.A
 		err = fmt.Errorf("xdr.Asset.Extract error: %w", err)
 		return err
 	}
+
+	prefix = o.FormatPrefix(prefix)
 	result[prefix+"asset_type"] = assetType
 
 	if a.Type == xdr.AssetTypeAssetTypeNative {
+		result[prefix+"asset_id"] = int64(-5706705804583548011)
 		return nil
 	}
 
 	result[prefix+"asset_code"] = code
 	result[prefix+"asset_issuer"] = issuer
+	result[prefix+"asset_id"] = o.FarmHashAsset(code, issuer, assetType)
+
 	return nil
 }
 
-func (o *LedgerOperation) switchContractIdPreimageType(contractIdPreimage xdr.ContractIdPreimage) map[string]interface{} {
+func (o *LedgerOperation) switchContractIdPreimageType(contractIdPreimage xdr.ContractIdPreimage) (map[string]interface{}, error) {
 	details := map[string]interface{}{}
 
 	switch contractIdPreimage.Type {
@@ -1330,21 +1330,25 @@ func (o *LedgerOperation) switchContractIdPreimageType(contractIdPreimage xdr.Co
 		fromAddress := contractIdPreimage.MustFromAddress()
 		address, err := fromAddress.Address.String()
 		if err != nil {
-			panic(fmt.Errorf("error obtaining address for: %s", contractIdPreimage.Type))
+			return details, err
 		}
 		details["from"] = "address"
 		details["address"] = address
 	case xdr.ContractIdPreimageTypeContractIdPreimageFromAsset:
 		details["from"] = "asset"
 		details["asset"] = contractIdPreimage.MustFromAsset().StringCanonical()
+		err := o.addAssetDetails(details, contractIdPreimage.MustFromAsset(), "")
+		if err != nil {
+			return details, err
+		}
 	default:
 		panic(fmt.Errorf("unknown contract id type: %s", contractIdPreimage.Type))
 	}
 
-	return details
+	return details, nil
 }
 
-func (o *LedgerOperation) ConvertStroopValueToReal(input xdr.Int64) float64 {
+func (o *LedgerOperation) ConvertStroopValueToReal(input int64) float64 {
 	output, _ := big.NewRat(int64(input), int64(10000000)).Float64()
 	return output
 }
diff --git a/ingest/ledger_operation_test.go b/ingest/ledger_operation_test.go
index 83978ee7fa..d7127104dc 100644
--- a/ingest/ledger_operation_test.go
+++ b/ingest/ledger_operation_test.go
@@ -1091,12 +1091,12 @@ func resultTestOutput() []testOutput {
 				"funder":           "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
 				"funder_muxed":     "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
 				"funder_muxed_id":  uint64(123),
-				"starting_balance": 2.5},
+				"starting_balance": int64(25000000)},
 		},
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"amount":        float64(35),
+				"amount":        int64(350000000),
 				"asset_code":    "USDT",
 				"asset_id":      int64(-8205667356306085451),
 				"asset_issuer":  "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
@@ -1109,7 +1109,7 @@ func resultTestOutput() []testOutput {
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"amount":        float64(35),
+				"amount":        int64(350000000),
 				"asset_id":      int64(-5706705804583548011),
 				"asset_type":    "native",
 				"from":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
@@ -1120,7 +1120,7 @@ func resultTestOutput() []testOutput {
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"amount":     895.14959,
+				"amount":     int64(8951495900),
 				"asset_id":   int64(-5706705804583548011),
 				"asset_type": "native",
 				"from":       "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN",
@@ -1131,16 +1131,16 @@ func resultTestOutput() []testOutput {
 						AssetType:   "credit_alphanum4",
 					},
 				},
-				"source_amount":     float64(0),
+				"source_amount":     int64(0),
 				"source_asset_id":   int64(-5706705804583548011),
 				"source_asset_type": "native",
-				"source_max":        895.14959,
+				"source_max":        int64(8951495900),
 				"to":                "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
 		},
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"amount":            float64(76.586),
+				"amount":            int64(765860000),
 				"buying_asset_id":   int64(-5706705804583548011),
 				"buying_asset_type": "native",
 				"offer_id":          int64(0),
@@ -1157,7 +1157,7 @@ func resultTestOutput() []testOutput {
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"amount":              float64(63.1595),
+				"amount":              int64(631595000),
 				"buying_asset_code":   "USDT",
 				"buying_asset_id":     int64(-8205667356306085451),
 				"buying_asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
@@ -1195,7 +1195,7 @@ func resultTestOutput() []testOutput {
 				"asset_id":         int64(6690054458235693884),
 				"asset_issuer":     "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 				"asset_type":       "credit_alphanum4",
-				"limit":            5e+10,
+				"limit":            int64(500000000000000000),
 				"trustee":          "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 				"trustor":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
 				"trustor_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
@@ -1205,7 +1205,7 @@ func resultTestOutput() []testOutput {
 			err: nil,
 			result: map[string]interface{}{
 				"asset_type":        "liquidity_pool_shares",
-				"limit":             5e+10,
+				"limit":             int64(500000000000000000),
 				"liquidity_pool_id": "1c261d6c75930204a73b480c3020ab525e9be48ce93de6194cf69fb06f07452d",
 				"trustor":           "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
 				"trustor_muxed":     "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
@@ -1252,7 +1252,7 @@ func resultTestOutput() []testOutput {
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"amount":            float64(765.4501001),
+				"amount":            int64(7654501001),
 				"buying_asset_id":   int64(-5706705804583548011),
 				"buying_asset_type": "native",
 				"offer_id":          int64(100),
@@ -1269,7 +1269,7 @@ func resultTestOutput() []testOutput {
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"amount":          float64(64),
+				"amount":          int64(640000000),
 				"asset_id":        int64(-5706705804583548011),
 				"asset_type":      "native",
 				"destination_min": "428.0460538",
@@ -1283,7 +1283,7 @@ func resultTestOutput() []testOutput {
 						AssetType:   "credit_alphanum4",
 					},
 				},
-				"source_amount":     0.1598182,
+				"source_amount":     int64(1598182),
 				"source_asset_id":   int64(-5706705804583548011),
 				"source_asset_type": "native",
 				"to":                "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
@@ -1291,8 +1291,12 @@ func resultTestOutput() []testOutput {
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"amount": 123456.789,
-				"asset":  "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"amount":       int64(1234567890000),
+				"asset":        "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"asset_code":   "USDT",
+				"asset_id":     int64(-8205667356306085451),
+				"asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"asset_type":   "credit_alphanum4",
 				"claimants": []Claimant{
 					{
 						Destination: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
@@ -1369,7 +1373,7 @@ func resultTestOutput() []testOutput {
 		{
 			err: nil,
 			result: map[string]interface{}{
-				"amount":       0.1598182,
+				"amount":       int64(1598182),
 				"asset_code":   "USDT",
 				"asset_id":     int64(-8205667356306085451),
 				"asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
@@ -1410,13 +1414,13 @@ func resultTestOutput() []testOutput {
 				"reserve_a_asset_issuer":   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 				"reserve_a_asset_type":     "credit_alphanum4",
 				"reserve_a_deposit_amount": 1e-07,
-				"reserve_a_max_amount":     0.0001,
+				"reserve_a_max_amount":     int64(1000),
 				"reserve_b_asset_code":     "USDT",
 				"reserve_b_asset_id":       int64(-8205667356306085451),
 				"reserve_b_asset_issuer":   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 				"reserve_b_asset_type":     "credit_alphanum4",
 				"reserve_b_deposit_amount": 1e-07,
-				"reserve_b_max_amount":     1e-05,
+				"reserve_b_max_amount":     int64(100),
 				"shares_received":          1e-07,
 			},
 		},
@@ -1428,15 +1432,15 @@ func resultTestOutput() []testOutput {
 				"reserve_a_asset_id":        int64(-8205667356306085451),
 				"reserve_a_asset_issuer":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 				"reserve_a_asset_type":      "credit_alphanum4",
-				"reserve_a_min_amount":      1e-07,
-				"reserve_a_withdraw_amount": -1e-07,
+				"reserve_a_min_amount":      int64(1),
+				"reserve_a_withdraw_amount": int64(-1),
 				"reserve_b_asset_code":      "USDT",
 				"reserve_b_asset_id":        int64(-8205667356306085451),
 				"reserve_b_asset_issuer":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 				"reserve_b_asset_type":      "credit_alphanum4",
-				"reserve_b_min_amount":      1e-07,
-				"reserve_b_withdraw_amount": -1e-07,
-				"shares":                    4e-07,
+				"reserve_b_min_amount":      int64(1),
+				"reserve_b_withdraw_amount": int64(-1),
+				"shares":                    int64(4),
 			},
 		},
 		{
@@ -1484,6 +1488,10 @@ func resultTestOutput() []testOutput {
 			err: nil,
 			result: map[string]interface{}{
 				"asset":           "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"asset_code":      "USDT",
+				"asset_id":        int64(-8205667356306085451),
+				"asset_issuer":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"asset_type":      "credit_alphanum4",
 				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
 				"from":            "asset",
 				"function":        "HostFunctionTypeHostFunctionTypeCreateContract",
@@ -1495,6 +1503,10 @@ func resultTestOutput() []testOutput {
 			err: nil,
 			result: map[string]interface{}{
 				"asset":           "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"asset_code":      "USDT",
+				"asset_id":        int64(-8205667356306085451),
+				"asset_issuer":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				"asset_type":      "credit_alphanum4",
 				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
 				"from":            "asset",
 				"function":        "HostFunctionTypeHostFunctionTypeCreateContractV2",

From 86d5d78136fd3088261b1834e223b621cae53675 Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Mon, 3 Feb 2025 17:52:20 -0500
Subject: [PATCH 04/12] Add structs for each operation

---
 ingest/account_merge_details.go               |   47 +
 ingest/allow_trust_details.go                 |   58 +
 ...begin_sponsoring_future_reserve_details.go |   18 +
 ingest/bump_sequence_details.go               |   18 +
 ingest/change_trust_details.go                |   85 ++
 ingest/claim_claimable_balance_details.go     |   46 +
 ingest/clawback_claimable_balance_details.go  |   25 +
 ingest/clawback_details.go                    |   47 +
 ingest/create_account_details.go              |   36 +
 ingest/create_claimable_balance_details.go    |   38 +
 ingest/create_passive_sell_offer_details.go   |   60 +
 .../end_sponsoring_future_reserve_details.go  |   51 +
 ingest/extend_footprint_ttl_details.go        |   38 +
 ingest/inflation_details.go                   |    7 +
 ingest/invoke_host_function_details.go        |  144 +++
 ingest/ledger_operation.go                    | 1077 ++---------------
 ingest/liquidity_pool_deposit_details.go      |  110 ++
 ingest/liquidity_pool_withdraw_details.go     |   85 ++
 ingest/manage_buy_offer_details.go            |   62 +
 ingest/manage_data_details.go                 |   28 +
 ingest/manage_sell_offer_details.go           |   63 +
 ingest/path_payment_strict_receive_details.go |  100 ++
 ingest/path_payment_strict_send_details.go    |  100 ++
 ingest/payment_details.go                     |   60 +
 ingest/restore_footprint_details.go           |   36 +
 ingest/revoke_sponsorship_details.go          |   37 +
 ingest/set_options_details.go                 |   97 ++
 ingest/set_trustline_flags_details.go         |   73 ++
 28 files changed, 1700 insertions(+), 946 deletions(-)
 create mode 100644 ingest/account_merge_details.go
 create mode 100644 ingest/allow_trust_details.go
 create mode 100644 ingest/begin_sponsoring_future_reserve_details.go
 create mode 100644 ingest/bump_sequence_details.go
 create mode 100644 ingest/change_trust_details.go
 create mode 100644 ingest/claim_claimable_balance_details.go
 create mode 100644 ingest/clawback_claimable_balance_details.go
 create mode 100644 ingest/clawback_details.go
 create mode 100644 ingest/create_account_details.go
 create mode 100644 ingest/create_claimable_balance_details.go
 create mode 100644 ingest/create_passive_sell_offer_details.go
 create mode 100644 ingest/end_sponsoring_future_reserve_details.go
 create mode 100644 ingest/extend_footprint_ttl_details.go
 create mode 100644 ingest/inflation_details.go
 create mode 100644 ingest/invoke_host_function_details.go
 create mode 100644 ingest/liquidity_pool_deposit_details.go
 create mode 100644 ingest/liquidity_pool_withdraw_details.go
 create mode 100644 ingest/manage_buy_offer_details.go
 create mode 100644 ingest/manage_data_details.go
 create mode 100644 ingest/manage_sell_offer_details.go
 create mode 100644 ingest/path_payment_strict_receive_details.go
 create mode 100644 ingest/path_payment_strict_send_details.go
 create mode 100644 ingest/payment_details.go
 create mode 100644 ingest/restore_footprint_details.go
 create mode 100644 ingest/revoke_sponsorship_details.go
 create mode 100644 ingest/set_options_details.go
 create mode 100644 ingest/set_trustline_flags_details.go

diff --git a/ingest/account_merge_details.go b/ingest/account_merge_details.go
new file mode 100644
index 0000000000..fc70291188
--- /dev/null
+++ b/ingest/account_merge_details.go
@@ -0,0 +1,47 @@
+package ingest
+
+import "fmt"
+
+type AccountMergeDetail struct {
+	Account        string `json:"account"`
+	AccountMuxed   string `json:"account_muxed"`
+	AccountMuxedID uint64 `json:"account_muxed_id"`
+	Into           string `json:"into"`
+	IntoMuxed      string `json:"into_muxed"`
+	IntoMuxedID    uint64 `json:"into_muxed_id"`
+}
+
+func (o *LedgerOperation) AccountMergeDetails() (AccountMergeDetail, error) {
+	destinationAccount, ok := o.Operation.Body.GetDestination()
+	if !ok {
+		return AccountMergeDetail{}, fmt.Errorf("could not access Destination info for this operation (index %d)", o.OperationIndex)
+	}
+
+	accountMergeDetail := AccountMergeDetail{
+		Account: o.SourceAccount(),
+		Into:    destinationAccount.Address(),
+	}
+
+	var err error
+	var accountMuxed string
+	var accountMuxedID uint64
+	accountMuxed, accountMuxedID, err = getMuxedAccountDetails(o.sourceAccountXDR())
+	if err != nil {
+		return AccountMergeDetail{}, err
+	}
+
+	accountMergeDetail.AccountMuxed = accountMuxed
+	accountMergeDetail.AccountMuxedID = accountMuxedID
+
+	var intoMuxed string
+	var intoMuxedID uint64
+	intoMuxed, intoMuxedID, err = getMuxedAccountDetails(destinationAccount)
+	if err != nil {
+		return AccountMergeDetail{}, err
+	}
+
+	accountMergeDetail.IntoMuxed = intoMuxed
+	accountMergeDetail.IntoMuxedID = intoMuxedID
+
+	return accountMergeDetail, nil
+}
diff --git a/ingest/allow_trust_details.go b/ingest/allow_trust_details.go
new file mode 100644
index 0000000000..df06278738
--- /dev/null
+++ b/ingest/allow_trust_details.go
@@ -0,0 +1,58 @@
+package ingest
+
+import (
+	"fmt"
+
+	"github.com/stellar/go/xdr"
+)
+
+type AllowTrustDetail struct {
+	AssetCode                      string `json:"asset_code"`
+	AssetIssuer                    string `json:"asset_issuer"`
+	AssetType                      string `json:"asset_type"`
+	Trustor                        string `json:"trustor"`
+	Trustee                        string `json:"trustee"`
+	TrusteeMuxed                   string `json:"trustee_muxed"`
+	TrusteeMuxedID                 uint64 `json:"trustee_muxed_id"`
+	Authorize                      bool   `json:"authorize"`
+	AuthorizeToMaintainLiabilities bool   `json:"authorize_to_maintain_liabilities"`
+	ClawbackEnabled                bool   `json:"clawback_enabled"`
+}
+
+func (o *LedgerOperation) AllowTrustDetails() (AllowTrustDetail, error) {
+	op, ok := o.Operation.Body.GetAllowTrustOp()
+	if !ok {
+		return AllowTrustDetail{}, fmt.Errorf("could not access AllowTrust info for this operation (index %d)", o.OperationIndex)
+	}
+
+	allowTrustDetail := AllowTrustDetail{
+		Trustor:                        op.Trustor.Address(),
+		Trustee:                        o.SourceAccount(),
+		Authorize:                      xdr.TrustLineFlags(op.Authorize).IsAuthorizedToMaintainLiabilitiesFlag(),
+		AuthorizeToMaintainLiabilities: xdr.TrustLineFlags(op.Authorize).IsAuthorizedToMaintainLiabilitiesFlag(),
+		ClawbackEnabled:                xdr.TrustLineFlags(op.Authorize).IsClawbackEnabledFlag(),
+	}
+
+	var err error
+	var assetCode, assetIssuer, assetType string
+	err = op.Asset.ToAsset(o.sourceAccountXDR().ToAccountId()).Extract(&assetType, &assetCode, &assetIssuer)
+	if err != nil {
+		return AllowTrustDetail{}, err
+	}
+
+	allowTrustDetail.AssetCode = assetCode
+	allowTrustDetail.AssetIssuer = assetIssuer
+	allowTrustDetail.AssetType = assetType
+
+	var trusteeMuxed string
+	var trusteeMuxedID uint64
+	trusteeMuxed, trusteeMuxedID, err = getMuxedAccountDetails(o.sourceAccountXDR())
+	if err != nil {
+		return AllowTrustDetail{}, err
+	}
+
+	allowTrustDetail.TrusteeMuxed = trusteeMuxed
+	allowTrustDetail.TrusteeMuxedID = trusteeMuxedID
+
+	return allowTrustDetail, nil
+}
diff --git a/ingest/begin_sponsoring_future_reserve_details.go b/ingest/begin_sponsoring_future_reserve_details.go
new file mode 100644
index 0000000000..ea0c255468
--- /dev/null
+++ b/ingest/begin_sponsoring_future_reserve_details.go
@@ -0,0 +1,18 @@
+package ingest
+
+import "fmt"
+
+type BeginSponsoringFutureReservesDetail struct {
+	SponsoredID string `json:"sponsored_id"`
+}
+
+func (o *LedgerOperation) BeginSponsoringFutureReservesDetails() (BeginSponsoringFutureReservesDetail, error) {
+	op, ok := o.Operation.Body.GetBeginSponsoringFutureReservesOp()
+	if !ok {
+		return BeginSponsoringFutureReservesDetail{}, fmt.Errorf("could not access BeginSponsoringFutureReserves info for this operation (index %d)", o.OperationIndex)
+	}
+
+	return BeginSponsoringFutureReservesDetail{
+		SponsoredID: op.SponsoredId.Address(),
+	}, nil
+}
diff --git a/ingest/bump_sequence_details.go b/ingest/bump_sequence_details.go
new file mode 100644
index 0000000000..c3e3d2e86e
--- /dev/null
+++ b/ingest/bump_sequence_details.go
@@ -0,0 +1,18 @@
+package ingest
+
+import "fmt"
+
+type BumpSequenceDetails struct {
+	BumpTo string `json:"bump_to"`
+}
+
+func (o *LedgerOperation) BumpSequenceDetails() (BumpSequenceDetails, error) {
+	op, ok := o.Operation.Body.GetBumpSequenceOp()
+	if !ok {
+		return BumpSequenceDetails{}, fmt.Errorf("could not access BumpSequence info for this operation (index %d)", o.OperationIndex)
+	}
+
+	return BumpSequenceDetails{
+		BumpTo: fmt.Sprintf("%d", op.BumpTo),
+	}, nil
+}
diff --git a/ingest/change_trust_details.go b/ingest/change_trust_details.go
new file mode 100644
index 0000000000..a3302146f7
--- /dev/null
+++ b/ingest/change_trust_details.go
@@ -0,0 +1,85 @@
+package ingest
+
+import (
+	"fmt"
+
+	"github.com/stellar/go/xdr"
+)
+
+type ChangeTrustDetail struct {
+	AssetCode       string `json:"asset_code"`
+	AssetIssuer     string `json:"asset_issuer"`
+	AssetType       string `json:"asset_type"`
+	LiquidityPoolID string `json:"liquidity_pool_id"`
+	Limit           int64  `json:"limit"`
+	Trustee         string `json:"trustee"`
+	Trustor         string `json:"trustor"`
+	TrustorMuxed    string `json:"trustor_muxed"`
+	TrustorMuxedID  uint64 `json:"trustor_muxed_id"`
+}
+
+func (o *LedgerOperation) ChangeTrustDetails() (ChangeTrustDetail, error) {
+	op, ok := o.Operation.Body.GetChangeTrustOp()
+	if !ok {
+		return ChangeTrustDetail{}, fmt.Errorf("could not access GetChangeTrust info for this operation (index %d)", o.OperationIndex)
+	}
+
+	var err error
+	changeTrustDetail := ChangeTrustDetail{
+		Trustor: o.SourceAccount(),
+		Limit:   int64(op.Limit),
+	}
+
+	if op.Line.Type == xdr.AssetTypeAssetTypePoolShare {
+		changeTrustDetail.AssetType, changeTrustDetail.LiquidityPoolID, err = getLiquidityPoolAssetDetails(*op.Line.LiquidityPool)
+		if err != nil {
+			return ChangeTrustDetail{}, err
+		}
+	} else {
+		var assetCode, assetIssuer, assetType string
+		err = op.Line.ToAsset().Extract(&assetType, &assetCode, &assetIssuer)
+		if err != nil {
+			return ChangeTrustDetail{}, err
+		}
+
+		changeTrustDetail.AssetCode = assetCode
+		changeTrustDetail.AssetIssuer = assetIssuer
+		changeTrustDetail.AssetType = assetType
+		changeTrustDetail.Trustee = assetIssuer
+	}
+
+	var trustorMuxed string
+	var trustorMuxedID uint64
+	trustorMuxed, trustorMuxedID, err = getMuxedAccountDetails(o.sourceAccountXDR())
+	if err != nil {
+		return ChangeTrustDetail{}, err
+	}
+
+	changeTrustDetail.TrustorMuxed = trustorMuxed
+	changeTrustDetail.TrustorMuxedID = trustorMuxedID
+
+	return changeTrustDetail, nil
+}
+
+func getLiquidityPoolAssetDetails(lpp xdr.LiquidityPoolParameters) (string, string, error) {
+	if lpp.Type != xdr.LiquidityPoolTypeLiquidityPoolConstantProduct {
+		return "", "", fmt.Errorf("unknown liquidity pool type %d", lpp.Type)
+	}
+
+	cp := lpp.ConstantProduct
+
+	var err error
+	var poolID xdr.PoolId
+	var poolIDString string
+	poolID, err = xdr.NewPoolId(cp.AssetA, cp.AssetB, cp.Fee)
+	if err != nil {
+		return "", "", err
+	}
+
+	poolIDString, err = PoolIDToString(poolID)
+	if err != nil {
+		return "", "", err
+	}
+
+	return "liquidity_pool_shares", poolIDString, nil
+}
diff --git a/ingest/claim_claimable_balance_details.go b/ingest/claim_claimable_balance_details.go
new file mode 100644
index 0000000000..2125d31e6b
--- /dev/null
+++ b/ingest/claim_claimable_balance_details.go
@@ -0,0 +1,46 @@
+package ingest
+
+import (
+	"fmt"
+
+	"github.com/stellar/go/xdr"
+)
+
+type ClaimClaimableBalanceDetail struct {
+	BalanceID       string `json:"balance_id"`
+	Claimant        string `json:"claimant"`
+	ClaimantMuxed   string `json:"claimant_muxed"`
+	ClaimantMuxedID uint64 `json:"claimant_muxed_id"`
+}
+
+func (o *LedgerOperation) ClaimClaimableBalanceDetails() (ClaimClaimableBalanceDetail, error) {
+	op, ok := o.Operation.Body.GetClaimClaimableBalanceOp()
+	if !ok {
+		return ClaimClaimableBalanceDetail{}, fmt.Errorf("could not access ClaimClaimableBalance info for this operation (index %d)", o.OperationIndex)
+	}
+
+	claimClaimableBalanceDetail := ClaimClaimableBalanceDetail{
+		Claimant: o.SourceAccount(),
+	}
+
+	var err error
+	var balanceID string
+	balanceID, err = xdr.MarshalBase64(op.BalanceId)
+	if err != nil {
+		return ClaimClaimableBalanceDetail{}, err
+	}
+
+	claimClaimableBalanceDetail.BalanceID = balanceID
+
+	var claimantMuxed string
+	var claimantMuxedID uint64
+	claimantMuxed, claimantMuxedID, err = getMuxedAccountDetails(o.sourceAccountXDR())
+	if err != nil {
+		return ClaimClaimableBalanceDetail{}, err
+	}
+
+	claimClaimableBalanceDetail.ClaimantMuxed = claimantMuxed
+	claimClaimableBalanceDetail.ClaimantMuxedID = claimantMuxedID
+
+	return claimClaimableBalanceDetail, nil
+}
diff --git a/ingest/clawback_claimable_balance_details.go b/ingest/clawback_claimable_balance_details.go
new file mode 100644
index 0000000000..5a8e19f796
--- /dev/null
+++ b/ingest/clawback_claimable_balance_details.go
@@ -0,0 +1,25 @@
+package ingest
+
+import (
+	"fmt"
+
+	"github.com/stellar/go/xdr"
+)
+
+type ClawbackClaimableBalanceDetail struct {
+	BalanceID string `json:"balance_id"`
+}
+
+func (o *LedgerOperation) ClawbackClaimableBalanceDetails() (ClawbackClaimableBalanceDetail, error) {
+	op, ok := o.Operation.Body.GetClawbackClaimableBalanceOp()
+	if !ok {
+		return ClawbackClaimableBalanceDetail{}, fmt.Errorf("could not access ClawbackClaimableBalance info for this operation (index %d)", o.OperationIndex)
+	}
+
+	balanceID, err := xdr.MarshalBase64(op.BalanceId)
+	if err != nil {
+		return ClawbackClaimableBalanceDetail{}, err
+	}
+
+	return ClawbackClaimableBalanceDetail{BalanceID: balanceID}, nil
+}
diff --git a/ingest/clawback_details.go b/ingest/clawback_details.go
new file mode 100644
index 0000000000..f9dc0f6fd4
--- /dev/null
+++ b/ingest/clawback_details.go
@@ -0,0 +1,47 @@
+package ingest
+
+import "fmt"
+
+type ClawbackDetail struct {
+	AssetCode   string `json:"asset_code"`
+	AssetIssuer string `json:"asset_issuer"`
+	AssetType   string `json:"asset_type"`
+	From        string `json:"from"`
+	FromMuxed   string `json:"from_muxed"`
+	FromMuxedID uint64 `json:"from_muxed_id"`
+	Amount      int64  `json:"amount"`
+}
+
+func (o *LedgerOperation) ClawbackDetails() (ClawbackDetail, error) {
+	op, ok := o.Operation.Body.GetClawbackOp()
+	if !ok {
+		return ClawbackDetail{}, fmt.Errorf("could not access Clawback info for this operation (index %d)", o.OperationIndex)
+	}
+
+	clawbackDetail := ClawbackDetail{
+		Amount: int64(op.Amount),
+	}
+
+	var err error
+	var assetCode, assetIssuer, assetType string
+	err = op.Asset.Extract(&assetType, &assetCode, &assetIssuer)
+	if err != nil {
+		return ClawbackDetail{}, err
+	}
+
+	clawbackDetail.AssetCode = assetCode
+	clawbackDetail.AssetIssuer = assetIssuer
+	clawbackDetail.AssetType = assetType
+
+	var fromMuxed string
+	var fromMuxedID uint64
+	fromMuxed, fromMuxedID, err = getMuxedAccountDetails(op.From)
+	if err != nil {
+		return ClawbackDetail{}, err
+	}
+
+	clawbackDetail.FromMuxed = fromMuxed
+	clawbackDetail.FromMuxedID = fromMuxedID
+
+	return clawbackDetail, nil
+}
diff --git a/ingest/create_account_details.go b/ingest/create_account_details.go
new file mode 100644
index 0000000000..9a04de1be6
--- /dev/null
+++ b/ingest/create_account_details.go
@@ -0,0 +1,36 @@
+package ingest
+
+import (
+	"fmt"
+)
+
+type CreateAccountDetail struct {
+	Account         string `json:"account"`
+	StartingBalance int64  `json:"starting_balance"`
+	Funder          string `json:"funder"`
+	FunderMuxed     string `json:"funder_muxed"`
+	FunderMuxedID   uint64 `json:"funder_muxed_id"`
+}
+
+func (o *LedgerOperation) CreateAccountDetails() (CreateAccountDetail, error) {
+	op, ok := o.Operation.Body.GetCreateAccountOp()
+	if !ok {
+		return CreateAccountDetail{}, fmt.Errorf("could not access CreateAccount info for this operation (index %d)", o.OperationIndex)
+	}
+
+	createAccountDetail := CreateAccountDetail{
+		Account:         op.Destination.Address(),
+		StartingBalance: int64(op.StartingBalance),
+		Funder:          o.SourceAccount(),
+	}
+
+	funderMuxed, funderMuxedID, err := getMuxedAccountDetails(o.sourceAccountXDR())
+	if err != nil {
+		return CreateAccountDetail{}, err
+	}
+
+	createAccountDetail.FunderMuxed = funderMuxed
+	createAccountDetail.FunderMuxedID = funderMuxedID
+
+	return createAccountDetail, nil
+}
diff --git a/ingest/create_claimable_balance_details.go b/ingest/create_claimable_balance_details.go
new file mode 100644
index 0000000000..795ed5edf0
--- /dev/null
+++ b/ingest/create_claimable_balance_details.go
@@ -0,0 +1,38 @@
+package ingest
+
+import (
+	"fmt"
+)
+
+type CreateClaimableBalanceDetail struct {
+	AssetCode   string     `json:"asset_code"`
+	AssetIssuer string     `json:"asset_issuer"`
+	AssetType   string     `json:"asset_type"`
+	Amount      int64      `json:"amount"`
+	Claimants   []Claimant `json:"claimants"`
+}
+
+func (o *LedgerOperation) CreateClaimableBalanceDetails() (CreateClaimableBalanceDetail, error) {
+	op, ok := o.Operation.Body.GetCreateClaimableBalanceOp()
+	if !ok {
+		return CreateClaimableBalanceDetail{}, fmt.Errorf("could not access CreateClaimableBalance info for this operation (index %d)", o.OperationIndex)
+	}
+
+	createClaimableBalanceDetail := CreateClaimableBalanceDetail{
+		Claimants: transformClaimants(op.Claimants),
+		Amount:    int64(op.Amount),
+	}
+
+	var err error
+	var assetCode, assetIssuer, assetType string
+	err = op.Asset.Extract(&assetType, &assetCode, &assetIssuer)
+	if err != nil {
+		return CreateClaimableBalanceDetail{}, err
+	}
+
+	createClaimableBalanceDetail.AssetCode = assetCode
+	createClaimableBalanceDetail.AssetIssuer = assetIssuer
+	createClaimableBalanceDetail.AssetType = assetType
+
+	return createClaimableBalanceDetail, nil
+}
diff --git a/ingest/create_passive_sell_offer_details.go b/ingest/create_passive_sell_offer_details.go
new file mode 100644
index 0000000000..1532cb7d07
--- /dev/null
+++ b/ingest/create_passive_sell_offer_details.go
@@ -0,0 +1,60 @@
+package ingest
+
+import (
+	"fmt"
+	"strconv"
+)
+
+type CreatePassiveSellOfferDetail struct {
+	Amount             int64   `json:"amount"`
+	PriceN             int32   `json:"price_n"`
+	PriceD             int32   `json:"price_d"`
+	Price              float64 `json:"price"`
+	BuyingAssetCode    string  `json:"buying_asset_code"`
+	BuyingAssetIssuer  string  `json:"buying_asset_issuer"`
+	BuyingAssetType    string  `json:"buying_asset_type"`
+	SellingAssetCode   string  `json:"selling_asset_code"`
+	SellingAssetIssuer string  `json:"selling_asset_issuer"`
+	SellingAssetType   string  `json:"selling_asset_type"`
+}
+
+func (o *LedgerOperation) CreatePassiveSellOfferDetails() (CreatePassiveSellOfferDetail, error) {
+	op, ok := o.Operation.Body.GetCreatePassiveSellOfferOp()
+	if !ok {
+		return CreatePassiveSellOfferDetail{}, fmt.Errorf("could not access CreatePassiveSellOffer info for this operation (index %d)", o.OperationIndex)
+	}
+
+	createPassiveSellOffer := CreatePassiveSellOfferDetail{
+		Amount: int64(op.Amount),
+		PriceN: int32(op.Price.N),
+		PriceD: int32(op.Price.D),
+	}
+
+	var err error
+	createPassiveSellOffer.Price, err = strconv.ParseFloat(op.Price.String(), 64)
+	if err != nil {
+		return CreatePassiveSellOfferDetail{}, err
+	}
+
+	var buyingAssetCode, buyingAssetIssuer, buyingAssetType string
+	err = op.Buying.Extract(&buyingAssetType, &buyingAssetCode, &buyingAssetIssuer)
+	if err != nil {
+		return CreatePassiveSellOfferDetail{}, err
+	}
+
+	createPassiveSellOffer.BuyingAssetCode = buyingAssetCode
+	createPassiveSellOffer.BuyingAssetIssuer = buyingAssetIssuer
+	createPassiveSellOffer.BuyingAssetType = buyingAssetType
+
+	var sellingAssetCode, sellingAssetIssuer, sellingAssetType string
+	err = op.Selling.Extract(&sellingAssetType, &sellingAssetCode, &sellingAssetIssuer)
+	if err != nil {
+		return CreatePassiveSellOfferDetail{}, err
+	}
+
+	createPassiveSellOffer.SellingAssetCode = sellingAssetCode
+	createPassiveSellOffer.SellingAssetIssuer = sellingAssetIssuer
+	createPassiveSellOffer.SellingAssetType = sellingAssetType
+
+	return createPassiveSellOffer, nil
+}
diff --git a/ingest/end_sponsoring_future_reserve_details.go b/ingest/end_sponsoring_future_reserve_details.go
new file mode 100644
index 0000000000..5a9b58df9e
--- /dev/null
+++ b/ingest/end_sponsoring_future_reserve_details.go
@@ -0,0 +1,51 @@
+package ingest
+
+type EndSponsoringFutureReserveDetail struct {
+	BeginSponsor        string `json:"begin_sponsor"`
+	BeginSponsorMuxed   string `json:"begin_sponsor_muxed"`
+	BeginSponsorMuxedID uint64 `json:"begin_sponsor_muxed_id"`
+}
+
+func (o *LedgerOperation) EndSponsoringFutureReserveDetails() (EndSponsoringFutureReserveDetail, error) {
+	var endSponsoringFutureReserveDetail EndSponsoringFutureReserveDetail
+
+	beginSponsorOp := o.findInitatingBeginSponsoringOp()
+	if beginSponsorOp != nil {
+		endSponsoringFutureReserveDetail.BeginSponsor = o.SourceAccount()
+
+		var err error
+		var beginSponsorMuxed string
+		var beginSponsorMuxedID uint64
+		beginSponsorMuxed, beginSponsorMuxedID, err = getMuxedAccountDetails(o.sourceAccountXDR())
+		if err != nil {
+			return EndSponsoringFutureReserveDetail{}, err
+		}
+
+		endSponsoringFutureReserveDetail.BeginSponsorMuxed = beginSponsorMuxed
+		endSponsoringFutureReserveDetail.BeginSponsorMuxedID = beginSponsorMuxedID
+	}
+
+	return endSponsoringFutureReserveDetail, nil
+}
+
+func (o *LedgerOperation) findInitatingBeginSponsoringOp() *SponsorshipOutput {
+	if !o.Transaction.Successful() {
+		// Failed transactions may not have a compliant sandwich structure
+		// we can rely on (e.g. invalid nesting or a being operation with the wrong sponsoree ID)
+		// and thus we bail out since we could return incorrect information.
+		return nil
+	}
+	sponsoree := o.sourceAccountXDR().ToAccountId()
+	operations := o.Transaction.Envelope.Operations()
+	for i := int(o.OperationIndex) - 1; i >= 0; i-- {
+		if beginOp, ok := operations[i].Body.GetBeginSponsoringFutureReservesOp(); ok &&
+			beginOp.SponsoredId.Address() == sponsoree.Address() {
+			result := SponsorshipOutput{
+				Operation:      operations[i],
+				OperationIndex: uint32(i),
+			}
+			return &result
+		}
+	}
+	return nil
+}
diff --git a/ingest/extend_footprint_ttl_details.go b/ingest/extend_footprint_ttl_details.go
new file mode 100644
index 0000000000..8889077e03
--- /dev/null
+++ b/ingest/extend_footprint_ttl_details.go
@@ -0,0 +1,38 @@
+package ingest
+
+import "fmt"
+
+type ExtendFootprintTtlDetail struct {
+	Type             string   `json:"type"`
+	ExtendTo         uint32   `json:"extend_to"`
+	LedgerKeyHash    []string `json:"ledger_key_hash"`
+	ContractID       string   `json:"contract_id"`
+	ContractCodeHash string   `json:"contract_code_hash"`
+}
+
+func (o *LedgerOperation) ExtendFootprintTtlDetails() (ExtendFootprintTtlDetail, error) {
+	op, ok := o.Operation.Body.GetExtendFootprintTtlOp()
+	if !ok {
+		return ExtendFootprintTtlDetail{}, fmt.Errorf("could not access ExtendFootprintTtl info for this operation (index %d)", o.OperationIndex)
+	}
+
+	extendFootprintTtlDetail := ExtendFootprintTtlDetail{
+		Type:          "extend_footprint_ttl",
+		ExtendTo:      uint32(op.ExtendTo),
+		LedgerKeyHash: o.Transaction.LedgerKeyHashFromTxEnvelope(),
+	}
+
+	var contractID string
+	contractID, ok = o.Transaction.contractIdFromTxEnvelope()
+	if ok {
+		extendFootprintTtlDetail.ContractID = contractID
+	}
+
+	var contractCodeHash string
+	contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+	if ok {
+		extendFootprintTtlDetail.ContractCodeHash = contractCodeHash
+	}
+
+	return extendFootprintTtlDetail, nil
+}
diff --git a/ingest/inflation_details.go b/ingest/inflation_details.go
new file mode 100644
index 0000000000..6eda8adea1
--- /dev/null
+++ b/ingest/inflation_details.go
@@ -0,0 +1,7 @@
+package ingest
+
+type InflationDetail struct{}
+
+func (o *LedgerOperation) InflationDetails() (InflationDetail, error) {
+	return InflationDetail{}, nil
+}
diff --git a/ingest/invoke_host_function_details.go b/ingest/invoke_host_function_details.go
new file mode 100644
index 0000000000..8a37365602
--- /dev/null
+++ b/ingest/invoke_host_function_details.go
@@ -0,0 +1,144 @@
+package ingest
+
+import (
+	"fmt"
+
+	"github.com/stellar/go/xdr"
+)
+
+type InvokeHostFunctionDetail struct {
+	Function            string                `json:"function"`
+	Type                string                `json:"type"`
+	LedgerKeyHash       []string              `json:"ledger_key_hash"`
+	ContractID          string                `json:"contract_id"`
+	ContractCodeHash    string                `json:"contract_code_hash"`
+	Parameters          []map[string]string   `json:"parameters"`
+	ParametersDecoded   []map[string]string   `json:"parameters_decoded"`
+	AssetBalanceChanges []BalanceChangeDetail `json:"asset_balance_changes"`
+	From                string                `json:"from"`
+	Address             string                `json:"address"`
+	AssetCode           string                `json:"asset_code"`
+	AssetIssuer         string                `json:"asset_issuer"`
+	AssetType           string                `json:"asset_type"`
+}
+
+func (o *LedgerOperation) InvokeHostFunctionDetails() (InvokeHostFunctionDetail, error) {
+	op, ok := o.Operation.Body.GetInvokeHostFunctionOp()
+	if !ok {
+		return InvokeHostFunctionDetail{}, fmt.Errorf("could not access InvokeHostFunction info for this operation (index %d)", o.OperationIndex)
+	}
+
+	invokeHostFunctionDetail := InvokeHostFunctionDetail{
+		Function: op.HostFunction.Type.String(),
+	}
+
+	switch op.HostFunction.Type {
+	case xdr.HostFunctionTypeHostFunctionTypeInvokeContract:
+		invokeArgs := op.HostFunction.MustInvokeContract()
+		args := make([]xdr.ScVal, 0, len(invokeArgs.Args)+2)
+		args = append(args, xdr.ScVal{Type: xdr.ScValTypeScvAddress, Address: &invokeArgs.ContractAddress})
+		args = append(args, xdr.ScVal{Type: xdr.ScValTypeScvSymbol, Sym: &invokeArgs.FunctionName})
+		args = append(args, invokeArgs.Args...)
+
+		invokeHostFunctionDetail.Type = "invoke_contract"
+
+		contractId, err := invokeArgs.ContractAddress.String()
+		if err != nil {
+			return InvokeHostFunctionDetail{}, err
+		}
+
+		invokeHostFunctionDetail.LedgerKeyHash = o.Transaction.LedgerKeyHashFromTxEnvelope()
+		invokeHostFunctionDetail.ContractID = contractId
+
+		var contractCodeHash string
+		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+		if ok {
+			invokeHostFunctionDetail.ContractCodeHash = contractCodeHash
+		}
+
+		// TODO: Parameters should be processed with xdr2json
+		invokeHostFunctionDetail.Parameters, invokeHostFunctionDetail.ParametersDecoded = o.serializeParameters(args)
+
+		balanceChanges, err := o.parseAssetBalanceChangesFromContractEvents()
+		if err != nil {
+			return InvokeHostFunctionDetail{}, err
+		}
+
+		invokeHostFunctionDetail.AssetBalanceChanges = balanceChanges
+
+	case xdr.HostFunctionTypeHostFunctionTypeCreateContract:
+		args := op.HostFunction.MustCreateContract()
+
+		invokeHostFunctionDetail.Type = "create_contract"
+		invokeHostFunctionDetail.LedgerKeyHash = o.Transaction.LedgerKeyHashFromTxEnvelope()
+
+		var contractID string
+		contractID, ok = o.Transaction.contractIdFromTxEnvelope()
+		if ok {
+			invokeHostFunctionDetail.ContractID = contractID
+		}
+
+		var contractCodeHash string
+		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+		if ok {
+			invokeHostFunctionDetail.ContractCodeHash = contractCodeHash
+		}
+
+		preImageDetails, err := switchContractIdPreimageType(args.ContractIdPreimage)
+		if err != nil {
+			return InvokeHostFunctionDetail{}, nil
+		}
+
+		invokeHostFunctionDetail.From = preImageDetails.From
+		invokeHostFunctionDetail.Address = preImageDetails.Address
+		invokeHostFunctionDetail.AssetCode = preImageDetails.AssetCode
+		invokeHostFunctionDetail.AssetIssuer = preImageDetails.AssetIssuer
+		invokeHostFunctionDetail.AssetType = preImageDetails.AssetType
+	case xdr.HostFunctionTypeHostFunctionTypeUploadContractWasm:
+		invokeHostFunctionDetail.Type = "upload_wasm"
+		invokeHostFunctionDetail.LedgerKeyHash = o.Transaction.LedgerKeyHashFromTxEnvelope()
+
+		var contractCodeHash string
+		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+		if ok {
+			invokeHostFunctionDetail.ContractCodeHash = contractCodeHash
+		}
+	case xdr.HostFunctionTypeHostFunctionTypeCreateContractV2:
+		args := op.HostFunction.MustCreateContractV2()
+
+		invokeHostFunctionDetail.Type = "create_contract_v2"
+		invokeHostFunctionDetail.LedgerKeyHash = o.Transaction.LedgerKeyHashFromTxEnvelope()
+
+		var contractID string
+		contractID, ok = o.Transaction.contractIdFromTxEnvelope()
+		if ok {
+			invokeHostFunctionDetail.ContractID = contractID
+		}
+
+		var contractCodeHash string
+		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+		if ok {
+			invokeHostFunctionDetail.ContractCodeHash = contractCodeHash
+		}
+
+		// ConstructorArgs is a list of ScVals
+		// This will initially be handled the same as InvokeContractParams until a different
+		// model is found necessary.
+		invokeHostFunctionDetail.Parameters, invokeHostFunctionDetail.ParametersDecoded = o.serializeParameters(args.ConstructorArgs)
+
+		preImageDetails, err := switchContractIdPreimageType(args.ContractIdPreimage)
+		if err != nil {
+			return InvokeHostFunctionDetail{}, nil
+		}
+
+		invokeHostFunctionDetail.From = preImageDetails.From
+		invokeHostFunctionDetail.Address = preImageDetails.Address
+		invokeHostFunctionDetail.AssetCode = preImageDetails.AssetCode
+		invokeHostFunctionDetail.AssetIssuer = preImageDetails.AssetIssuer
+		invokeHostFunctionDetail.AssetType = preImageDetails.AssetType
+	default:
+		return InvokeHostFunctionDetail{}, fmt.Errorf("unknown host function type: %s", op.HostFunction.Type)
+	}
+
+	return invokeHostFunctionDetail, nil
+}
diff --git a/ingest/ledger_operation.go b/ingest/ledger_operation.go
index cf7f29f799..77b831448d 100644
--- a/ingest/ledger_operation.go
+++ b/ingest/ledger_operation.go
@@ -4,7 +4,6 @@ import (
 	"encoding/base64"
 	"fmt"
 	"math/big"
-	"strconv"
 
 	"github.com/dgryski/go-farm"
 	"github.com/stellar/go/amount"
@@ -31,10 +30,7 @@ func (o *LedgerOperation) sourceAccountXDR() xdr.MuxedAccount {
 
 func (o *LedgerOperation) SourceAccount() string {
 	muxedAccount := o.sourceAccountXDR()
-	providedID := muxedAccount.ToAccountId()
-	pointerToID := &providedID
-
-	return pointerToID.Address()
+	return muxedAccount.ToAccountId().Address()
 }
 
 func (o *LedgerOperation) Type() int32 {
@@ -90,9 +86,9 @@ func (o *LedgerOperation) OperationTraceCode() (string, error) {
 	return operationTraceCode, nil
 }
 
-func (o *LedgerOperation) OperationDetails() (map[string]interface{}, error) {
+func (o *LedgerOperation) OperationDetails() (interface{}, error) {
 	var err error
-	details := map[string]interface{}{}
+	var details interface{}
 
 	switch o.Operation.Body.Type {
 	case xdr.OperationTypeCreateAccount:
@@ -201,7 +197,7 @@ func (o *LedgerOperation) OperationDetails() (map[string]interface{}, error) {
 			return details, err
 		}
 	case xdr.OperationTypeSetTrustLineFlags:
-		details, err = o.SetTrustLineFlagsDetails()
+		details, err = o.SetTrustlineFlagsDetails()
 		if err != nil {
 			return details, err
 		}
@@ -237,705 +233,85 @@ func (o *LedgerOperation) OperationDetails() (map[string]interface{}, error) {
 	return details, nil
 }
 
-func (o *LedgerOperation) CreateAccountDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetCreateAccountOp()
-	if !ok {
-		return details, fmt.Errorf("could not access CreateAccount info for this operation (index %d)", o.OperationIndex)
-	}
-
-	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "funder"); err != nil {
-		return details, err
-	}
-	details["account"] = op.Destination.Address()
-	details["starting_balance"] = int64(op.StartingBalance)
-
-	return details, nil
-}
+func getMuxedAccountDetails(a xdr.MuxedAccount) (string, uint64, error) {
+	var err error
+	var muxedAccountAddress string
+	var muxedAccountID uint64
 
-func (o *LedgerOperation) addAccountAndMuxedAccountDetails(result map[string]interface{}, a xdr.MuxedAccount, prefix string) error {
-	account_id := a.ToAccountId()
-	result[prefix] = account_id.Address()
-	prefix = o.FormatPrefix(prefix)
 	if a.Type == xdr.CryptoKeyTypeKeyTypeMuxedEd25519 {
-		muxedAccountAddress, err := a.GetAddress()
+		muxedAccountAddress, err = a.GetAddress()
 		if err != nil {
-			return err
+			return "", 0, err
 		}
-		result[prefix+"muxed"] = muxedAccountAddress
-		muxedAccountId, err := a.GetId()
+		muxedAccountID, err = a.GetId()
 		if err != nil {
-			return err
-		}
-		result[prefix+"muxed_id"] = muxedAccountId
-	}
-	return nil
-}
-
-func (o *LedgerOperation) PaymentDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetPaymentOp()
-	if !ok {
-		return details, fmt.Errorf("could not access Payment info for this operation (index %d)", o.OperationIndex)
-	}
-
-	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "from"); err != nil {
-		return details, err
-	}
-	if err := o.addAccountAndMuxedAccountDetails(details, op.Destination, "to"); err != nil {
-		return details, err
-	}
-	details["amount"] = int64(op.Amount)
-	if err := o.addAssetDetails(details, op.Asset, ""); err != nil {
-		return details, err
-	}
-
-	return details, nil
-}
-
-func (o *LedgerOperation) PathPaymentStrictReceiveDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetPathPaymentStrictReceiveOp()
-	if !ok {
-		return details, fmt.Errorf("could not access PathPaymentStrictReceive info for this operation (index %d)", o.OperationIndex)
-	}
-
-	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "from"); err != nil {
-		return details, err
-	}
-	if err := o.addAccountAndMuxedAccountDetails(details, op.Destination, "to"); err != nil {
-		return details, err
-	}
-	details["amount"] = int64(op.DestAmount)
-	details["source_amount"] = amount.String(0)
-	details["source_max"] = int64(op.SendMax)
-	if err := o.addAssetDetails(details, op.DestAsset, ""); err != nil {
-		return details, err
-	}
-	if err := o.addAssetDetails(details, op.SendAsset, "source"); err != nil {
-		return details, err
-	}
-
-	if o.Transaction.Successful() {
-		allOperationResults, ok := o.Transaction.Result.OperationResults()
-		if !ok {
-			return details, fmt.Errorf("could not access any results for this transaction")
-		}
-		currentOperationResult := allOperationResults[o.OperationIndex]
-		resultBody, ok := currentOperationResult.GetTr()
-		if !ok {
-			return details, fmt.Errorf("could not access result body for this operation (index %d)", o.OperationIndex)
-		}
-		result, ok := resultBody.GetPathPaymentStrictReceiveResult()
-		if !ok {
-			return details, fmt.Errorf("could not access PathPaymentStrictReceive result info for this operation (index %d)", o.OperationIndex)
-		}
-		details["source_amount"] = int64(result.SendAmount())
-	}
-
-	details["path"] = o.TransformPath(op.Path)
-	return details, nil
-}
-
-func (o *LedgerOperation) PathPaymentStrictSendDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetPathPaymentStrictSendOp()
-	if !ok {
-		return details, fmt.Errorf("could not access PathPaymentStrictSend info for this operation (index %d)", o.OperationIndex)
-	}
-
-	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "from"); err != nil {
-		return details, err
-	}
-	if err := o.addAccountAndMuxedAccountDetails(details, op.Destination, "to"); err != nil {
-		return details, err
-	}
-	details["amount"] = float64(0)
-	details["source_amount"] = int64(op.SendAmount)
-	details["destination_min"] = amount.String(op.DestMin)
-	if err := o.addAssetDetails(details, op.DestAsset, ""); err != nil {
-		return details, err
-	}
-	if err := o.addAssetDetails(details, op.SendAsset, "source"); err != nil {
-		return details, err
-	}
-
-	if o.Transaction.Successful() {
-		allOperationResults, ok := o.Transaction.Result.OperationResults()
-		if !ok {
-			return details, fmt.Errorf("could not access any results for this transaction")
-		}
-		currentOperationResult := allOperationResults[o.OperationIndex]
-		resultBody, ok := currentOperationResult.GetTr()
-		if !ok {
-			return details, fmt.Errorf("could not access result body for this operation (index %d)", o.OperationIndex)
-		}
-		result, ok := resultBody.GetPathPaymentStrictSendResult()
-		if !ok {
-			return details, fmt.Errorf("could not access GetPathPaymentStrictSendResult result info for this operation (index %d)", o.OperationIndex)
-		}
-		details["amount"] = int64(result.DestAmount())
-	}
-
-	details["path"] = o.TransformPath(op.Path)
-
-	return details, nil
-}
-func (o *LedgerOperation) ManageBuyOfferDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetManageBuyOfferOp()
-	if !ok {
-		return details, fmt.Errorf("could not access ManageBuyOffer info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["offer_id"] = int64(op.OfferId)
-	details["amount"] = int64(op.BuyAmount)
-	if err := o.addPriceDetails(details, op.Price, ""); err != nil {
-		return details, err
-	}
-
-	if err := o.addAssetDetails(details, op.Buying, "buying"); err != nil {
-		return details, err
-	}
-	if err := o.addAssetDetails(details, op.Selling, "selling"); err != nil {
-		return details, err
-	}
-
-	return details, nil
-}
-
-func (o *LedgerOperation) addPriceDetails(result map[string]interface{}, price xdr.Price, prefix string) error {
-	prefix = o.FormatPrefix(prefix)
-	parsedPrice, err := strconv.ParseFloat(price.String(), 64)
-	if err != nil {
-		return err
-	}
-	result[prefix+"price"] = parsedPrice
-	result[prefix+"price_r"] = Price{
-		Numerator:   int32(price.N),
-		Denominator: int32(price.D),
-	}
-	return nil
-}
-
-func (o *LedgerOperation) ManageSellOfferDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetManageSellOfferOp()
-	if !ok {
-		return details, fmt.Errorf("could not access ManageSellOffer info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["offer_id"] = int64(op.OfferId)
-	details["amount"] = int64(op.Amount)
-	if err := o.addPriceDetails(details, op.Price, ""); err != nil {
-		return details, err
-	}
-
-	if err := o.addAssetDetails(details, op.Buying, "buying"); err != nil {
-		return details, err
-	}
-	if err := o.addAssetDetails(details, op.Selling, "selling"); err != nil {
-		return details, err
-	}
-
-	return details, nil
-}
-func (o *LedgerOperation) CreatePassiveSellOfferDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetCreatePassiveSellOfferOp()
-	if !ok {
-		return details, fmt.Errorf("could not access CreatePassiveSellOffer info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["amount"] = int64(op.Amount)
-	if err := o.addPriceDetails(details, op.Price, ""); err != nil {
-		return details, err
-	}
-
-	if err := o.addAssetDetails(details, op.Buying, "buying"); err != nil {
-		return details, err
-	}
-	if err := o.addAssetDetails(details, op.Selling, "selling"); err != nil {
-		return details, err
-	}
-
-	return details, nil
-}
-func (o *LedgerOperation) SetOptionsDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetSetOptionsOp()
-	if !ok {
-		return details, fmt.Errorf("could not access GetSetOptions info for this operation (index %d)", o.OperationIndex)
-	}
-
-	if op.InflationDest != nil {
-		details["inflation_dest"] = op.InflationDest.Address()
-	}
-
-	if op.SetFlags != nil && *op.SetFlags > 0 {
-		o.addOperationFlagToOperationDetails(details, uint32(*op.SetFlags), "set")
-	}
-
-	if op.ClearFlags != nil && *op.ClearFlags > 0 {
-		o.addOperationFlagToOperationDetails(details, uint32(*op.ClearFlags), "clear")
-	}
-
-	if op.MasterWeight != nil {
-		details["master_key_weight"] = uint32(*op.MasterWeight)
-	}
-
-	if op.LowThreshold != nil {
-		details["low_threshold"] = uint32(*op.LowThreshold)
-	}
-
-	if op.MedThreshold != nil {
-		details["med_threshold"] = uint32(*op.MedThreshold)
-	}
-
-	if op.HighThreshold != nil {
-		details["high_threshold"] = uint32(*op.HighThreshold)
-	}
-
-	if op.HomeDomain != nil {
-		details["home_domain"] = string(*op.HomeDomain)
-	}
-
-	if op.Signer != nil {
-		details["signer_key"] = op.Signer.Key.Address()
-		details["signer_weight"] = uint32(op.Signer.Weight)
-	}
-
-	return details, nil
-}
-
-func (o *LedgerOperation) addOperationFlagToOperationDetails(result map[string]interface{}, flag uint32, prefix string) {
-	intFlags := make([]int32, 0)
-	stringFlags := make([]string, 0)
-
-	if (int64(flag) & int64(xdr.AccountFlagsAuthRequiredFlag)) > 0 {
-		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthRequiredFlag))
-		stringFlags = append(stringFlags, "auth_required")
-	}
-
-	if (int64(flag) & int64(xdr.AccountFlagsAuthRevocableFlag)) > 0 {
-		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthRevocableFlag))
-		stringFlags = append(stringFlags, "auth_revocable")
-	}
-
-	if (int64(flag) & int64(xdr.AccountFlagsAuthImmutableFlag)) > 0 {
-		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthImmutableFlag))
-		stringFlags = append(stringFlags, "auth_immutable")
-	}
-
-	if (int64(flag) & int64(xdr.AccountFlagsAuthClawbackEnabledFlag)) > 0 {
-		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthClawbackEnabledFlag))
-		stringFlags = append(stringFlags, "auth_clawback_enabled")
-	}
-
-	prefix = o.FormatPrefix(prefix)
-	result[prefix+"flags"] = intFlags
-	result[prefix+"flags_s"] = stringFlags
-}
-
-func (o *LedgerOperation) ChangeTrustDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetChangeTrustOp()
-	if !ok {
-		return details, fmt.Errorf("could not access GetChangeTrust info for this operation (index %d)", o.OperationIndex)
-	}
-
-	if op.Line.Type == xdr.AssetTypeAssetTypePoolShare {
-		if err := o.addLiquidityPoolAssetDetails(details, *op.Line.LiquidityPool); err != nil {
-			return details, err
-		}
-	} else {
-		if err := o.addAssetDetails(details, op.Line.ToAsset(), ""); err != nil {
-			return details, err
+			return "", 0, err
 		}
-		details["trustee"] = details["asset_issuer"]
-	}
-
-	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "trustor"); err != nil {
-		return details, err
-	}
-
-	details["limit"] = int64(op.Limit)
-
-	return details, nil
-}
-
-func (o *LedgerOperation) addLiquidityPoolAssetDetails(result map[string]interface{}, lpp xdr.LiquidityPoolParameters) error {
-	result["asset_type"] = "liquidity_pool_shares"
-	if lpp.Type != xdr.LiquidityPoolTypeLiquidityPoolConstantProduct {
-		return fmt.Errorf("unknown liquidity pool type %d", lpp.Type)
-	}
-	cp := lpp.ConstantProduct
-	poolID, err := xdr.NewPoolId(cp.AssetA, cp.AssetB, cp.Fee)
-	if err != nil {
-		return err
-	}
-
-	result["liquidity_pool_id"] = o.PoolIDToString(poolID)
-
-	return nil
-}
-
-func (o *LedgerOperation) AllowTrustDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetAllowTrustOp()
-	if !ok {
-		return details, fmt.Errorf("could not access AllowTrust info for this operation (index %d)", o.OperationIndex)
-	}
-
-	if err := o.addAssetDetails(details, op.Asset.ToAsset(o.sourceAccountXDR().ToAccountId()), ""); err != nil {
-		return details, err
-	}
-	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "trustee"); err != nil {
-		return details, err
-	}
-	details["trustor"] = op.Trustor.Address()
-	shouldAuth := xdr.TrustLineFlags(op.Authorize).IsAuthorized()
-	details["authorize"] = shouldAuth
-	shouldAuthLiabilities := xdr.TrustLineFlags(op.Authorize).IsAuthorizedToMaintainLiabilitiesFlag()
-	if shouldAuthLiabilities {
-		details["authorize_to_maintain_liabilities"] = shouldAuthLiabilities
-	}
-	shouldClawbackEnabled := xdr.TrustLineFlags(op.Authorize).IsClawbackEnabledFlag()
-	if shouldClawbackEnabled {
-		details["clawback_enabled"] = shouldClawbackEnabled
-	}
-
-	return details, nil
-}
-
-func (o *LedgerOperation) AccountMergeDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	destinationAccount, ok := o.Operation.Body.GetDestination()
-	if !ok {
-		return details, fmt.Errorf("could not access Destination info for this operation (index %d)", o.OperationIndex)
-	}
-
-	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "account"); err != nil {
-		return details, err
-	}
-	if err := o.addAccountAndMuxedAccountDetails(details, destinationAccount, "into"); err != nil {
-		return details, err
 	}
-
-	return details, nil
+	return muxedAccountAddress, muxedAccountID, nil
 }
 
-// Inflation operations don't have information that affects the details struct
-func (o *LedgerOperation) InflationDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	return details, nil
+type LedgerKeyDetail struct {
+	AccountID                string `json:"account_id"`
+	ClaimableBalanceID       string `json:"claimable_balance_id"`
+	DataAccountID            string `json:"data_account_id"`
+	DataName                 string `json:"data_name"`
+	OfferID                  int64  `json:"offer_id"`
+	TrustlineAccountID       string `json:"trustline_account_id"`
+	TrustlineLiquidityPoolID string `json:"trustline_liquidity_pool_id"`
+	TrustlineAssetCode       string `json:"trustline_asset_code"`
+	TrustlineAssetIssuer     string `json:"trustline_asset_issuer"`
+	TrustlineAssetType       string `json:"trustline_asset_type"`
+	LiquidityPoolID          string `json:"liquidity_pool_id"`
 }
 
-func (o *LedgerOperation) ManageDataDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetManageDataOp()
-	if !ok {
-		return details, fmt.Errorf("could not access GetManageData info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["name"] = string(op.DataName)
-	if op.DataValue != nil {
-		details["value"] = base64.StdEncoding.EncodeToString(*op.DataValue)
-	} else {
-		details["value"] = nil
-	}
-
-	return details, nil
-}
-
-func (o *LedgerOperation) BumpSequenceDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetBumpSequenceOp()
-	if !ok {
-		return details, fmt.Errorf("could not access BumpSequence info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["bump_to"] = fmt.Sprintf("%d", op.BumpTo)
-
-	return details, nil
-}
-func (o *LedgerOperation) CreateClaimableBalanceDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetCreateClaimableBalanceOp()
-	if !ok {
-		return details, fmt.Errorf("could not access CreateClaimableBalance info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["asset"] = op.Asset.StringCanonical()
-	err := o.addAssetDetails(details, op.Asset, "")
-	if err != nil {
-		return details, err
-	}
-
-	details["amount"] = int64(op.Amount)
-	details["claimants"] = o.TransformClaimants(op.Claimants)
-
-	return details, nil
-}
-
-func (o *LedgerOperation) ClaimClaimableBalanceDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetClaimClaimableBalanceOp()
-	if !ok {
-		return details, fmt.Errorf("could not access ClaimClaimableBalance info for this operation (index %d)", o.OperationIndex)
-	}
-
-	balanceID, err := xdr.MarshalHex(op.BalanceId)
-	if err != nil {
-		return details, fmt.Errorf("invalid balanceId in op: %d", o.OperationIndex)
-	}
-	details["balance_id"] = balanceID
-	if err := o.addAccountAndMuxedAccountDetails(details, o.sourceAccountXDR(), "claimant"); err != nil {
-		return details, err
-	}
-
-	return details, nil
-}
-
-func (o *LedgerOperation) BeginSponsoringFutureReservesDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetBeginSponsoringFutureReservesOp()
-	if !ok {
-		return details, fmt.Errorf("could not access BeginSponsoringFutureReserves info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["sponsored_id"] = op.SponsoredId.Address()
-
-	return details, nil
-}
-
-func (o *LedgerOperation) EndSponsoringFutureReserveDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	beginSponsorOp := o.findInitatingBeginSponsoringOp()
-	if beginSponsorOp != nil {
-		beginSponsorshipSource := o.sourceAccountXDR()
-		if err := o.addAccountAndMuxedAccountDetails(details, beginSponsorshipSource, "begin_sponsor"); err != nil {
-			return details, err
-		}
-	}
-
-	return details, nil
-}
-
-func (o *LedgerOperation) findInitatingBeginSponsoringOp() *SponsorshipOutput {
-	if !o.Transaction.Successful() {
-		// Failed transactions may not have a compliant sandwich structure
-		// we can rely on (e.g. invalid nesting or a being operation with the wrong sponsoree ID)
-		// and thus we bail out since we could return incorrect information.
-		return nil
-	}
-	sponsoree := o.sourceAccountXDR().ToAccountId()
-	operations := o.Transaction.Envelope.Operations()
-	for i := int(o.OperationIndex) - 1; i >= 0; i-- {
-		if beginOp, ok := operations[i].Body.GetBeginSponsoringFutureReservesOp(); ok &&
-			beginOp.SponsoredId.Address() == sponsoree.Address() {
-			result := SponsorshipOutput{
-				Operation:      operations[i],
-				OperationIndex: uint32(i),
-			}
-			return &result
-		}
-	}
-	return nil
-}
-
-func (o *LedgerOperation) RevokeSponsorshipDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetRevokeSponsorshipOp()
-	if !ok {
-		return details, fmt.Errorf("could not access RevokeSponsorship info for this operation (index %d)", o.OperationIndex)
-	}
-
-	switch op.Type {
-	case xdr.RevokeSponsorshipTypeRevokeSponsorshipLedgerEntry:
-		if err := o.addLedgerKeyToDetails(details, *op.LedgerKey); err != nil {
-			return details, err
-		}
-	case xdr.RevokeSponsorshipTypeRevokeSponsorshipSigner:
-		details["signer_account_id"] = op.Signer.AccountId.Address()
-		details["signer_key"] = op.Signer.SignerKey.Address()
-	}
-
-	return details, nil
-}
+func addLedgerKeyToDetails(ledgerKey xdr.LedgerKey) (LedgerKeyDetail, error) {
+	var err error
+	var ledgerKeyDetail LedgerKeyDetail
 
-func (o *LedgerOperation) addLedgerKeyToDetails(result map[string]interface{}, ledgerKey xdr.LedgerKey) error {
 	switch ledgerKey.Type {
 	case xdr.LedgerEntryTypeAccount:
-		result["account_id"] = ledgerKey.Account.AccountId.Address()
+		ledgerKeyDetail.AccountID = ledgerKey.Account.AccountId.Address()
 	case xdr.LedgerEntryTypeClaimableBalance:
-		marshalHex, err := xdr.MarshalHex(ledgerKey.ClaimableBalance.BalanceId)
+		var marshalHex string
+		marshalHex, err = xdr.MarshalHex(ledgerKey.ClaimableBalance.BalanceId)
 		if err != nil {
-			return fmt.Errorf("in claimable balance: %w", err)
+			return LedgerKeyDetail{}, fmt.Errorf("in claimable balance: %w", err)
 		}
-		result["claimable_balance_id"] = marshalHex
+		ledgerKeyDetail.ClaimableBalanceID = marshalHex
 	case xdr.LedgerEntryTypeData:
-		result["data_account_id"] = ledgerKey.Data.AccountId.Address()
-		result["data_name"] = string(ledgerKey.Data.DataName)
+		ledgerKeyDetail.DataAccountID = ledgerKey.Data.AccountId.Address()
+		ledgerKeyDetail.DataName = string(ledgerKey.Data.DataName)
 	case xdr.LedgerEntryTypeOffer:
-		result["offer_id"] = int64(ledgerKey.Offer.OfferId)
+		ledgerKeyDetail.OfferID = int64(ledgerKey.Offer.OfferId)
 	case xdr.LedgerEntryTypeTrustline:
-		result["trustline_account_id"] = ledgerKey.TrustLine.AccountId.Address()
+		ledgerKeyDetail.TrustlineAccountID = ledgerKey.TrustLine.AccountId.Address()
 		if ledgerKey.TrustLine.Asset.Type == xdr.AssetTypeAssetTypePoolShare {
-			result["trustline_liquidity_pool_id"] = o.PoolIDToString(*ledgerKey.TrustLine.Asset.LiquidityPoolId)
+			ledgerKeyDetail.TrustlineLiquidityPoolID, err = PoolIDToString(*ledgerKey.TrustLine.Asset.LiquidityPoolId)
+			if err != nil {
+				return LedgerKeyDetail{}, err
+			}
 		} else {
-			result["trustline_asset"] = ledgerKey.TrustLine.Asset.ToAsset().StringCanonical()
+			var assetCode, assetIssuer, assetType string
+			err = ledgerKey.TrustLine.Asset.ToAsset().Extract(&assetType, &assetCode, &assetIssuer)
+			if err != nil {
+				return LedgerKeyDetail{}, err
+			}
+
+			ledgerKeyDetail.TrustlineAssetCode = assetCode
+			ledgerKeyDetail.TrustlineAssetIssuer = assetIssuer
+			ledgerKeyDetail.TrustlineAssetType = assetType
 		}
 	case xdr.LedgerEntryTypeLiquidityPool:
-		result["liquidity_pool_id"] = o.PoolIDToString(ledgerKey.LiquidityPool.LiquidityPoolId)
-	}
-	return nil
-}
-
-func (o *LedgerOperation) ClawbackDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetClawbackOp()
-	if !ok {
-		return details, fmt.Errorf("could not access Clawback info for this operation (index %d)", o.OperationIndex)
-	}
-
-	if err := o.addAssetDetails(details, op.Asset, ""); err != nil {
-		return details, err
-	}
-	if err := o.addAccountAndMuxedAccountDetails(details, op.From, "from"); err != nil {
-		return details, err
-	}
-	details["amount"] = int64(op.Amount)
-
-	return details, nil
-}
-func (o *LedgerOperation) ClawbackClaimableBalanceDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetClawbackClaimableBalanceOp()
-	if !ok {
-		return details, fmt.Errorf("could not access ClawbackClaimableBalance info for this operation (index %d)", o.OperationIndex)
-	}
-
-	balanceID, err := xdr.MarshalHex(op.BalanceId)
-	if err != nil {
-		return details, fmt.Errorf("invalid balanceId in op: %d", o.OperationIndex)
-	}
-	details["balance_id"] = balanceID
-
-	return details, nil
-}
-func (o *LedgerOperation) SetTrustLineFlagsDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetSetTrustLineFlagsOp()
-	if !ok {
-		return details, fmt.Errorf("could not access SetTrustLineFlags info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["trustor"] = op.Trustor.Address()
-	if err := o.addAssetDetails(details, op.Asset, ""); err != nil {
-		return details, err
-	}
-	if op.SetFlags > 0 {
-		o.addTrustLineFlagToDetails(details, xdr.TrustLineFlags(op.SetFlags), "set")
-
-	}
-	if op.ClearFlags > 0 {
-		o.addTrustLineFlagToDetails(details, xdr.TrustLineFlags(op.ClearFlags), "clear")
-	}
-
-	return details, nil
-}
-
-func (o *LedgerOperation) addTrustLineFlagToDetails(result map[string]interface{}, f xdr.TrustLineFlags, prefix string) {
-	var (
-		n []int32
-		s []string
-	)
-
-	if f.IsAuthorized() {
-		n = append(n, int32(xdr.TrustLineFlagsAuthorizedFlag))
-		s = append(s, "authorized")
-	}
-
-	if f.IsAuthorizedToMaintainLiabilitiesFlag() {
-		n = append(n, int32(xdr.TrustLineFlagsAuthorizedToMaintainLiabilitiesFlag))
-		s = append(s, "authorized_to_maintain_liabilities")
-	}
-
-	if f.IsClawbackEnabledFlag() {
-		n = append(n, int32(xdr.TrustLineFlagsTrustlineClawbackEnabledFlag))
-		s = append(s, "clawback_enabled")
-	}
-
-	prefix = o.FormatPrefix(prefix)
-	result[prefix+"flags"] = n
-	result[prefix+"flags_s"] = s
-}
-
-func (o *LedgerOperation) LiquidityPoolDepositDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetLiquidityPoolDepositOp()
-	if !ok {
-		return details, fmt.Errorf("could not access LiquidityPoolDeposit info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["liquidity_pool_id"] = o.PoolIDToString(op.LiquidityPoolId)
-	var (
-		assetA, assetB         xdr.Asset
-		depositedA, depositedB xdr.Int64
-		sharesReceived         xdr.Int64
-	)
-	if o.Transaction.Successful() {
-		// we will use the defaults (omitted asset and 0 amounts) if the transaction failed
-		lp, delta, err := o.getLiquidityPoolAndProductDelta(&op.LiquidityPoolId)
+		ledgerKeyDetail.LiquidityPoolID, err = PoolIDToString(ledgerKey.LiquidityPool.LiquidityPoolId)
 		if err != nil {
-			return nil, err
+			return LedgerKeyDetail{}, err
 		}
-		params := lp.Body.ConstantProduct.Params
-		assetA, assetB = params.AssetA, params.AssetB
-		depositedA, depositedB = delta.ReserveA, delta.ReserveB
-		sharesReceived = delta.TotalPoolShares
-	}
-
-	// Process ReserveA Details
-	if err := o.addAssetDetails(details, assetA, "reserve_a"); err != nil {
-		return details, err
-	}
-	details["reserve_a_max_amount"] = int64(op.MaxAmountA)
-	depositA, err := strconv.ParseFloat(amount.String(depositedA), 64)
-	if err != nil {
-		return details, err
-	}
-	details["reserve_a_deposit_amount"] = depositA
-
-	//Process ReserveB Details
-	if err := o.addAssetDetails(details, assetB, "reserve_b"); err != nil {
-		return details, err
-	}
-	details["reserve_b_max_amount"] = int64(op.MaxAmountB)
-	depositB, err := strconv.ParseFloat(amount.String(depositedB), 64)
-	if err != nil {
-		return details, err
-	}
-	details["reserve_b_deposit_amount"] = depositB
-
-	if err := o.addPriceDetails(details, op.MinPrice, "min"); err != nil {
-		return details, err
-	}
-	if err := o.addPriceDetails(details, op.MaxPrice, "max"); err != nil {
-		return details, err
-	}
-
-	sharesToFloat, err := strconv.ParseFloat(amount.String(sharesReceived), 64)
-	if err != nil {
-		return details, err
 	}
-	details["shares_received"] = sharesToFloat
 
-	return details, nil
+	return ledgerKeyDetail, nil
 }
 
-// operation xdr.Operation, operationIndex int32, transaction LedgerTransaction, ledgerSeq int32
 func (o *LedgerOperation) getLiquidityPoolAndProductDelta(lpID *xdr.PoolId) (*xdr.LiquidityPoolEntry, *LiquidityPoolDelta, error) {
 	changes, err := o.Transaction.GetOperationChanges(uint32(o.OperationIndex))
 	if err != nil {
@@ -985,218 +361,6 @@ func (o *LedgerOperation) getLiquidityPoolAndProductDelta(lpID *xdr.PoolId) (*xd
 	return nil, nil, fmt.Errorf("liquidity pool change not found")
 }
 
-func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetLiquidityPoolWithdrawOp()
-	if !ok {
-		return details, fmt.Errorf("could not access LiquidityPoolWithdraw info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["liquidity_pool_id"] = o.PoolIDToString(op.LiquidityPoolId)
-	var (
-		assetA, assetB       xdr.Asset
-		receivedA, receivedB xdr.Int64
-	)
-	if o.Transaction.Successful() {
-		// we will use the defaults (omitted asset and 0 amounts) if the transaction failed
-		lp, delta, err := o.getLiquidityPoolAndProductDelta(&op.LiquidityPoolId)
-		if err != nil {
-			return nil, err
-		}
-		params := lp.Body.ConstantProduct.Params
-		assetA, assetB = params.AssetA, params.AssetB
-		receivedA, receivedB = -delta.ReserveA, -delta.ReserveB
-	}
-	// Process AssetA Details
-	if err := o.addAssetDetails(details, assetA, "reserve_a"); err != nil {
-		return details, err
-	}
-	details["reserve_a_min_amount"] = int64(op.MinAmountA)
-	details["reserve_a_withdraw_amount"] = int64(receivedA)
-
-	// Process AssetB Details
-	if err := o.addAssetDetails(details, assetB, "reserve_b"); err != nil {
-		return details, err
-	}
-	details["reserve_b_min_amount"] = int64(op.MinAmountB)
-	details["reserve_b_withdraw_amount"] = int64(receivedB)
-
-	details["shares"] = int64(op.Amount)
-
-	return details, nil
-}
-
-func (o *LedgerOperation) InvokeHostFunctionDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetInvokeHostFunctionOp()
-	if !ok {
-		return details, fmt.Errorf("could not access InvokeHostFunction info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["function"] = op.HostFunction.Type.String()
-
-	switch op.HostFunction.Type {
-	case xdr.HostFunctionTypeHostFunctionTypeInvokeContract:
-		invokeArgs := op.HostFunction.MustInvokeContract()
-		args := make([]xdr.ScVal, 0, len(invokeArgs.Args)+2)
-		args = append(args, xdr.ScVal{Type: xdr.ScValTypeScvAddress, Address: &invokeArgs.ContractAddress})
-		args = append(args, xdr.ScVal{Type: xdr.ScValTypeScvSymbol, Sym: &invokeArgs.FunctionName})
-		args = append(args, invokeArgs.Args...)
-
-		details["type"] = "invoke_contract"
-
-		contractId, err := invokeArgs.ContractAddress.String()
-		if err != nil {
-			return nil, err
-		}
-
-		details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
-		details["contract_id"] = contractId
-		var contractCodeHash string
-		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
-		if ok {
-			details["contract_code_hash"] = contractCodeHash
-		}
-
-		details["parameters"], details["parameters_decoded"] = o.serializeParameters(args)
-
-		balanceChanges, err := o.parseAssetBalanceChangesFromContractEvents()
-		if err != nil {
-			return nil, err
-		}
-
-		details["asset_balance_changes"] = balanceChanges
-
-	case xdr.HostFunctionTypeHostFunctionTypeCreateContract:
-		args := op.HostFunction.MustCreateContract()
-		details["type"] = "create_contract"
-
-		details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
-
-		var contractID string
-		contractID, ok = o.Transaction.contractIdFromTxEnvelope()
-		if ok {
-			details["contract_id"] = contractID
-		}
-
-		var contractCodeHash string
-		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
-		if ok {
-			details["contract_code_hash"] = contractCodeHash
-		}
-
-		preimageTypeMap, err := o.switchContractIdPreimageType(args.ContractIdPreimage)
-		if err != nil {
-			return details, nil
-		}
-
-		for key, val := range preimageTypeMap {
-			if _, ok := preimageTypeMap[key]; ok {
-				details[key] = val
-			}
-		}
-	case xdr.HostFunctionTypeHostFunctionTypeUploadContractWasm:
-		details["type"] = "upload_wasm"
-		details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
-
-		var contractCodeHash string
-		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
-		if ok {
-			details["contract_code_hash"] = contractCodeHash
-		}
-	case xdr.HostFunctionTypeHostFunctionTypeCreateContractV2:
-		args := op.HostFunction.MustCreateContractV2()
-		details["type"] = "create_contract_v2"
-
-		details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
-
-		var contractID string
-		contractID, ok = o.Transaction.contractIdFromTxEnvelope()
-		if ok {
-			details["contract_id"] = contractID
-		}
-
-		var contractCodeHash string
-		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
-		if ok {
-			details["contract_code_hash"] = contractCodeHash
-		}
-
-		// ConstructorArgs is a list of ScVals
-		// This will initially be handled the same as InvokeContractParams until a different
-		// model is found necessary.
-		constructorArgs := args.ConstructorArgs
-		details["parameters"], details["parameters_decoded"] = o.serializeParameters(constructorArgs)
-
-		preimageTypeMap, err := o.switchContractIdPreimageType(args.ContractIdPreimage)
-		if err != nil {
-			return details, nil
-		}
-
-		for key, val := range preimageTypeMap {
-			if _, ok := preimageTypeMap[key]; ok {
-				details[key] = val
-			}
-		}
-	default:
-		panic(fmt.Errorf("unknown host function type: %s", op.HostFunction.Type))
-	}
-
-	return details, nil
-}
-
-func (o *LedgerOperation) ExtendFootprintTtlDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	op, ok := o.Operation.Body.GetExtendFootprintTtlOp()
-	if !ok {
-		return details, fmt.Errorf("could not access ExtendFootprintTtl info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["type"] = "extend_footprint_ttl"
-	details["extend_to"] = int32(op.ExtendTo)
-
-	details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
-
-	var contractID string
-	contractID, ok = o.Transaction.contractIdFromTxEnvelope()
-	if ok {
-		details["contract_id"] = contractID
-	}
-
-	var contractCodeHash string
-	contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
-	if ok {
-		details["contract_code_hash"] = contractCodeHash
-	}
-
-	return details, nil
-}
-func (o *LedgerOperation) RestoreFootprintDetails() (map[string]interface{}, error) {
-	details := map[string]interface{}{}
-	_, ok := o.Operation.Body.GetRestoreFootprintOp()
-	if !ok {
-		return details, fmt.Errorf("could not access RestoreFootprint info for this operation (index %d)", o.OperationIndex)
-	}
-
-	details["type"] = "restore_footprint"
-
-	details["ledger_key_hash"] = o.Transaction.LedgerKeyHashFromTxEnvelope()
-
-	var contractID string
-	contractID, ok = o.Transaction.contractIdFromTxEnvelope()
-	if ok {
-		details["contract_id"] = contractID
-	}
-
-	var contractCodeHash string
-	contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
-	if ok {
-		details["contract_code_hash"] = contractCodeHash
-	}
-
-	return details, nil
-}
-
 func (o *LedgerOperation) serializeParameters(args []xdr.ScVal) ([]map[string]string, []map[string]string) {
 	params := make([]map[string]string, 0, len(args))
 	paramsDecoded := make([]map[string]string, 0, len(args))
@@ -1225,8 +389,8 @@ func (o *LedgerOperation) serializeParameters(args []xdr.ScVal) ([]map[string]st
 	return params, paramsDecoded
 }
 
-func (o *LedgerOperation) parseAssetBalanceChangesFromContractEvents() ([]map[string]interface{}, error) {
-	balanceChanges := []map[string]interface{}{}
+func (o *LedgerOperation) parseAssetBalanceChangesFromContractEvents() ([]BalanceChangeDetail, error) {
+	balanceChanges := []BalanceChangeDetail{}
 
 	diagnosticEvents, err := o.Transaction.GetDiagnosticEvents()
 	if err != nil {
@@ -1238,21 +402,44 @@ func (o *LedgerOperation) parseAssetBalanceChangesFromContractEvents() ([]map[st
 	for _, contractEvent := range o.filterEvents(diagnosticEvents) {
 		// Parse the xdr contract event to contractevents.StellarAssetContractEvent model
 
+		var err error
+		var balanceChangeDetail BalanceChangeDetail
+		var sacEvent contractevents.StellarAssetContractEvent
 		// has some convenience like to/from attributes are expressed in strkey format for accounts(G...) and contracts(C...)
-		if sacEvent, err := contractevents.NewStellarAssetContractEvent(&contractEvent, o.NetworkPassphrase); err == nil {
+		if sacEvent, err = contractevents.NewStellarAssetContractEvent(&contractEvent, o.NetworkPassphrase); err == nil {
 			switch sacEvent.GetType() {
 			case contractevents.EventTypeTransfer:
 				transferEvt := sacEvent.(*contractevents.TransferEvent)
-				balanceChanges = append(balanceChanges, o.createSACBalanceChangeEntry(transferEvt.From, transferEvt.To, transferEvt.Amount, transferEvt.Asset, "transfer"))
+				balanceChangeDetail, err = createSACBalanceChangeEntry(transferEvt.From, transferEvt.To, transferEvt.Amount, transferEvt.Asset, "transfer")
+				if err != nil {
+					return []BalanceChangeDetail{}, err
+				}
+
+				balanceChanges = append(balanceChanges, balanceChangeDetail)
 			case contractevents.EventTypeMint:
 				mintEvt := sacEvent.(*contractevents.MintEvent)
-				balanceChanges = append(balanceChanges, o.createSACBalanceChangeEntry("", mintEvt.To, mintEvt.Amount, mintEvt.Asset, "mint"))
+				balanceChangeDetail, err = createSACBalanceChangeEntry("", mintEvt.To, mintEvt.Amount, mintEvt.Asset, "mint")
+				if err != nil {
+					return []BalanceChangeDetail{}, err
+				}
+
+				balanceChanges = append(balanceChanges, balanceChangeDetail)
 			case contractevents.EventTypeClawback:
 				clawbackEvt := sacEvent.(*contractevents.ClawbackEvent)
-				balanceChanges = append(balanceChanges, o.createSACBalanceChangeEntry(clawbackEvt.From, "", clawbackEvt.Amount, clawbackEvt.Asset, "clawback"))
+				balanceChangeDetail, err = createSACBalanceChangeEntry(clawbackEvt.From, "", clawbackEvt.Amount, clawbackEvt.Asset, "clawback")
+				if err != nil {
+					return []BalanceChangeDetail{}, err
+				}
+
+				balanceChanges = append(balanceChanges, balanceChangeDetail)
 			case contractevents.EventTypeBurn:
 				burnEvt := sacEvent.(*contractevents.BurnEvent)
-				balanceChanges = append(balanceChanges, o.createSACBalanceChangeEntry(burnEvt.From, "", burnEvt.Amount, burnEvt.Asset, "burn"))
+				balanceChangeDetail, err = createSACBalanceChangeEntry(burnEvt.From, "", burnEvt.Amount, burnEvt.Asset, "burn")
+				if err != nil {
+					return []BalanceChangeDetail{}, err
+				}
+
+				balanceChanges = append(balanceChanges, balanceChangeDetail)
 			}
 		}
 	}
@@ -1271,6 +458,16 @@ func (o *LedgerOperation) filterEvents(diagnosticEvents []xdr.DiagnosticEvent) [
 	return filtered
 }
 
+type BalanceChangeDetail struct {
+	From        string `json:"from"`
+	To          string `json:"to"`
+	Type        string `json:"type"`
+	Amount      string `json:"amount"`
+	AssetCode   string `json:"asset_code"`
+	AssetIssuer string `json:"asset_issuer"`
+	AssetType   string `json:"asset_type"`
+}
+
 // fromAccount   - strkey format of contract or address
 // toAccount     - strkey format of contract or address, or nillable
 // amountChanged - absolute value that asset balance changed
@@ -1278,74 +475,62 @@ func (o *LedgerOperation) filterEvents(diagnosticEvents []xdr.DiagnosticEvent) [
 // changeType    - the type of source sac event that triggered this change
 //
 // return        - a balance changed record expressed as map of key/value's
-func (o *LedgerOperation) createSACBalanceChangeEntry(fromAccount string, toAccount string, amountChanged xdr.Int128Parts, asset xdr.Asset, changeType string) map[string]interface{} {
-	balanceChange := map[string]interface{}{}
+func createSACBalanceChangeEntry(fromAccount string, toAccount string, amountChanged xdr.Int128Parts, asset xdr.Asset, changeType string) (BalanceChangeDetail, error) {
+	balanceChangeDetail := BalanceChangeDetail{
+		Type:   changeType,
+		Amount: amount.String128(amountChanged),
+	}
 
 	if fromAccount != "" {
-		balanceChange["from"] = fromAccount
+		balanceChangeDetail.From = fromAccount
 	}
 	if toAccount != "" {
-		balanceChange["to"] = toAccount
+		balanceChangeDetail.To = toAccount
 	}
 
-	balanceChange["type"] = changeType
-	balanceChange["amount"] = amount.String128(amountChanged)
-	o.addAssetDetails(balanceChange, asset, "")
-	return balanceChange
-}
-
-// addAssetDetails sets the details for `a` on `result` using keys with `prefix`
-func (o *LedgerOperation) addAssetDetails(result map[string]interface{}, a xdr.Asset, prefix string) error {
-	var (
-		assetType string
-		code      string
-		issuer    string
-	)
-	err := a.Extract(&assetType, &code, &issuer)
+	var assetCode, assetIssuer, assetType string
+	err := asset.Extract(&assetType, &assetCode, &assetIssuer)
 	if err != nil {
-		err = fmt.Errorf("xdr.Asset.Extract error: %w", err)
-		return err
+		return BalanceChangeDetail{}, err
 	}
 
-	prefix = o.FormatPrefix(prefix)
-	result[prefix+"asset_type"] = assetType
-
-	if a.Type == xdr.AssetTypeAssetTypeNative {
-		result[prefix+"asset_id"] = int64(-5706705804583548011)
-		return nil
-	}
-
-	result[prefix+"asset_code"] = code
-	result[prefix+"asset_issuer"] = issuer
-	result[prefix+"asset_id"] = o.FarmHashAsset(code, issuer, assetType)
-
-	return nil
+	return balanceChangeDetail, nil
 }
 
-func (o *LedgerOperation) switchContractIdPreimageType(contractIdPreimage xdr.ContractIdPreimage) (map[string]interface{}, error) {
-	details := map[string]interface{}{}
+type PreImageDetails struct {
+	From        string `json:"from"`
+	Address     string `json:"address"`
+	AssetCode   string `json:"asset_code"`
+	AssetIssuer string `json:"asset_issuer"`
+	AssetType   string `json:"asset_type"`
+}
 
+func switchContractIdPreimageType(contractIdPreimage xdr.ContractIdPreimage) (PreImageDetails, error) {
 	switch contractIdPreimage.Type {
 	case xdr.ContractIdPreimageTypeContractIdPreimageFromAddress:
 		fromAddress := contractIdPreimage.MustFromAddress()
 		address, err := fromAddress.Address.String()
 		if err != nil {
-			return details, err
+			return PreImageDetails{}, err
 		}
-		details["from"] = "address"
-		details["address"] = address
+		return PreImageDetails{
+			From:    "address",
+			Address: address,
+		}, nil
 	case xdr.ContractIdPreimageTypeContractIdPreimageFromAsset:
-		details["from"] = "asset"
-		details["asset"] = contractIdPreimage.MustFromAsset().StringCanonical()
-		err := o.addAssetDetails(details, contractIdPreimage.MustFromAsset(), "")
-		if err != nil {
-			return details, err
-		}
+		var assetCode, assetIssuer, assetType string
+		contractIdPreimage.MustFromAsset().Extract(&assetType, &assetCode, &assetIssuer)
+
+		return PreImageDetails{
+			From:        "asset",
+			AssetCode:   assetCode,
+			AssetIssuer: assetIssuer,
+			AssetType:   assetType,
+		}, nil
+
 	default:
-		panic(fmt.Errorf("unknown contract id type: %s", contractIdPreimage.Type))
+		return PreImageDetails{}, fmt.Errorf("unknown contract id type: %s", contractIdPreimage.Type)
 	}
-
-	return details, nil
 }
 
 func (o *LedgerOperation) ConvertStroopValueToReal(input int64) float64 {
@@ -1400,8 +585,8 @@ type Price struct {
 	Denominator int32 `json:"d"`
 }
 
-func (o *LedgerOperation) PoolIDToString(id xdr.PoolId) string {
-	return xdr.Hash(id).HexString()
+func PoolIDToString(id xdr.PoolId) (string, error) {
+	return xdr.MarshalBase64(id)
 }
 
 type Claimant struct {
@@ -1409,7 +594,7 @@ type Claimant struct {
 	Predicate   xdr.ClaimPredicate `json:"predicate"`
 }
 
-func (o *LedgerOperation) TransformClaimants(claimants []xdr.Claimant) []Claimant {
+func transformClaimants(claimants []xdr.Claimant) []Claimant {
 	var transformed []Claimant
 	for _, c := range claimants {
 		switch c.Type {
diff --git a/ingest/liquidity_pool_deposit_details.go b/ingest/liquidity_pool_deposit_details.go
new file mode 100644
index 0000000000..92f4322a98
--- /dev/null
+++ b/ingest/liquidity_pool_deposit_details.go
@@ -0,0 +1,110 @@
+package ingest
+
+import (
+	"fmt"
+	"strconv"
+
+	"github.com/stellar/go/xdr"
+)
+
+type LiquidityPoolDepositDetail struct {
+	LiquidityPoolID       string  `json:"liquidity_pool_id"`
+	ReserveAAssetCode     string  `json:"reserve_a_asset_code"`
+	ReserveAAssetIssuer   string  `json:"reserve_a_asset_issuer"`
+	ReserveAAssetType     string  `json:"reserve_a_asset_type"`
+	ReserveAMaxAmount     int64   `json:"reserve_a_max_amount"`
+	ReserveADepositAmount int64   `json:"reserve_a_deposit_amount"`
+	ReserveBAssetCode     string  `json:"reserve_b_asset_code"`
+	ReserveBAssetIssuer   string  `json:"reserve_b_asset_issuer"`
+	ReserveBAssetType     string  `json:"reserve_b_asset_type"`
+	ReserveBMaxAmount     int64   `json:"reserve_b_max_amount"`
+	ReserveBDepositAmount int64   `json:"reserve_b_deposit_amount"`
+	MinPriceN             int32   `json:"min_price_n"`
+	MinPriceD             int32   `json:"min_price_d"`
+	MinPrice              float64 `json:"min_price"`
+	MaxPriceN             int32   `json:"max_price_n"`
+	MaxPriceD             int32   `json:"max_price_d"`
+	MaxPrice              float64 `json:"max_price"`
+	SharesReceived        int64   `json:"shares_received"`
+}
+
+func (o *LedgerOperation) LiquidityPoolDepositDetails() (LiquidityPoolDepositDetail, error) {
+	op, ok := o.Operation.Body.GetLiquidityPoolDepositOp()
+	if !ok {
+		return LiquidityPoolDepositDetail{}, fmt.Errorf("could not access LiquidityPoolDeposit info for this operation (index %d)", o.OperationIndex)
+	}
+
+	liquidityPoolDepositDetail := LiquidityPoolDepositDetail{
+		ReserveAMaxAmount: int64(op.MaxAmountA),
+		ReserveBMaxAmount: int64(op.MaxAmountB),
+		MinPriceN:         int32(op.MinPrice.N),
+		MinPriceD:         int32(op.MinPrice.D),
+		MaxPriceN:         int32(op.MaxPrice.N),
+		MaxPriceD:         int32(op.MaxPrice.D),
+	}
+
+	var err error
+	var liquidityPoolID string
+	liquidityPoolID, err = PoolIDToString(op.LiquidityPoolId)
+	if err != nil {
+		return LiquidityPoolDepositDetail{}, err
+	}
+
+	liquidityPoolDepositDetail.LiquidityPoolID = liquidityPoolID
+
+	var (
+		assetA, assetB         xdr.Asset
+		depositedA, depositedB xdr.Int64
+		sharesReceived         xdr.Int64
+	)
+	if o.Transaction.Successful() {
+		// we will use the defaults (omitted asset and 0 amounts) if the transaction failed
+		lp, delta, err := o.getLiquidityPoolAndProductDelta(&op.LiquidityPoolId)
+		if err != nil {
+			return LiquidityPoolDepositDetail{}, err
+		}
+
+		params := lp.Body.ConstantProduct.Params
+		assetA, assetB = params.AssetA, params.AssetB
+		depositedA, depositedB = delta.ReserveA, delta.ReserveB
+		sharesReceived = delta.TotalPoolShares
+	}
+
+	// Process ReserveA Details
+	var assetACode, assetAIssuer, assetAType string
+	err = assetA.Extract(&assetAType, &assetACode, &assetAIssuer)
+	if err != nil {
+		return LiquidityPoolDepositDetail{}, err
+	}
+
+	liquidityPoolDepositDetail.ReserveAAssetCode = assetACode
+	liquidityPoolDepositDetail.ReserveAAssetIssuer = assetAIssuer
+	liquidityPoolDepositDetail.ReserveAAssetType = assetAType
+	liquidityPoolDepositDetail.ReserveADepositAmount = int64(depositedA)
+
+	//Process ReserveB Details
+	var assetBCode, assetBIssuer, assetBType string
+	err = assetB.Extract(&assetBType, &assetBCode, &assetBIssuer)
+	if err != nil {
+		return LiquidityPoolDepositDetail{}, err
+	}
+
+	liquidityPoolDepositDetail.ReserveBAssetCode = assetBCode
+	liquidityPoolDepositDetail.ReserveBAssetIssuer = assetBIssuer
+	liquidityPoolDepositDetail.ReserveBAssetType = assetBType
+	liquidityPoolDepositDetail.ReserveBDepositAmount = int64(depositedB)
+
+	liquidityPoolDepositDetail.MinPrice, err = strconv.ParseFloat(op.MinPrice.String(), 64)
+	if err != nil {
+		return LiquidityPoolDepositDetail{}, err
+	}
+
+	liquidityPoolDepositDetail.MaxPrice, err = strconv.ParseFloat(op.MaxPrice.String(), 64)
+	if err != nil {
+		return LiquidityPoolDepositDetail{}, err
+	}
+
+	liquidityPoolDepositDetail.SharesReceived = int64(sharesReceived)
+
+	return liquidityPoolDepositDetail, nil
+}
diff --git a/ingest/liquidity_pool_withdraw_details.go b/ingest/liquidity_pool_withdraw_details.go
new file mode 100644
index 0000000000..c250494e3d
--- /dev/null
+++ b/ingest/liquidity_pool_withdraw_details.go
@@ -0,0 +1,85 @@
+package ingest
+
+import (
+	"fmt"
+
+	"github.com/stellar/go/xdr"
+)
+
+type LiquidityPoolWithdrawDetail struct {
+	LiquidityPoolID        string `json:"liquidity_pool_id"`
+	ReserveAAssetCode      string `json:"reserve_a_asset_code"`
+	ReserveAAssetIssuer    string `json:"reserve_a_asset_issuer"`
+	ReserveAAssetType      string `json:"reserve_a_asset_type"`
+	ReserveAMinAmount      int64  `json:"reserve_a_min_amount"`
+	ReserveAWithdrawAmount int64  `json:"reserve_a_withdraw_amount"`
+	ReserveBAssetCode      string `json:"reserve_b_asset_code"`
+	ReserveBAssetIssuer    string `json:"reserve_b_asset_issuer"`
+	ReserveBAssetType      string `json:"reserve_b_asset_type"`
+	ReserveBMinAmount      int64  `json:"reserve_b_min_amount"`
+	ReserveBWithdrawAmount int64  `json:"reserve_b_withdraw_amount"`
+	Shares                 int64  `json:"shares"`
+}
+
+func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (LiquidityPoolWithdrawDetail, error) {
+	op, ok := o.Operation.Body.GetLiquidityPoolWithdrawOp()
+	if !ok {
+		return LiquidityPoolWithdrawDetail{}, fmt.Errorf("could not access LiquidityPoolWithdraw info for this operation (index %d)", o.OperationIndex)
+	}
+
+	liquidityPoolWithdrawDetail := LiquidityPoolWithdrawDetail{
+		ReserveAMinAmount: int64(op.MinAmountA),
+		ReserveBMinAmount: int64(op.MinAmountB),
+		Shares:            int64(op.Amount),
+	}
+
+	var err error
+	var liquidityPoolID string
+	liquidityPoolID, err = PoolIDToString(op.LiquidityPoolId)
+	if err != nil {
+		return LiquidityPoolWithdrawDetail{}, err
+	}
+
+	liquidityPoolWithdrawDetail.LiquidityPoolID = liquidityPoolID
+
+	var (
+		assetA, assetB       xdr.Asset
+		receivedA, receivedB xdr.Int64
+	)
+	if o.Transaction.Successful() {
+		// we will use the defaults (omitted asset and 0 amounts) if the transaction failed
+		lp, delta, err := o.getLiquidityPoolAndProductDelta(&op.LiquidityPoolId)
+		if err != nil {
+			return LiquidityPoolWithdrawDetail{}, err
+		}
+		params := lp.Body.ConstantProduct.Params
+		assetA, assetB = params.AssetA, params.AssetB
+		receivedA, receivedB = -delta.ReserveA, -delta.ReserveB
+	}
+
+	// Process AssetA Details
+	var assetACode, assetAIssuer, assetAType string
+	err = assetA.Extract(&assetAType, &assetACode, &assetAIssuer)
+	if err != nil {
+		return LiquidityPoolWithdrawDetail{}, err
+	}
+
+	liquidityPoolWithdrawDetail.ReserveAAssetCode = assetACode
+	liquidityPoolWithdrawDetail.ReserveAAssetIssuer = assetAIssuer
+	liquidityPoolWithdrawDetail.ReserveAAssetType = assetAType
+	liquidityPoolWithdrawDetail.ReserveAWithdrawAmount = int64(receivedA)
+
+	// Process AssetB Details
+	var assetBCode, assetBIssuer, assetBType string
+	err = assetB.Extract(&assetBType, &assetBCode, &assetBIssuer)
+	if err != nil {
+		return LiquidityPoolWithdrawDetail{}, err
+	}
+
+	liquidityPoolWithdrawDetail.ReserveBAssetCode = assetBCode
+	liquidityPoolWithdrawDetail.ReserveBAssetIssuer = assetBIssuer
+	liquidityPoolWithdrawDetail.ReserveBAssetType = assetBType
+	liquidityPoolWithdrawDetail.ReserveAWithdrawAmount = int64(receivedB)
+
+	return liquidityPoolWithdrawDetail, nil
+}
diff --git a/ingest/manage_buy_offer_details.go b/ingest/manage_buy_offer_details.go
new file mode 100644
index 0000000000..21c9ac35ab
--- /dev/null
+++ b/ingest/manage_buy_offer_details.go
@@ -0,0 +1,62 @@
+package ingest
+
+import (
+	"fmt"
+	"strconv"
+)
+
+type ManageBuyOffer struct {
+	OfferID            int64   `json:"offer_id"`
+	Amount             int64   `json:"amount"`
+	PriceN             int32   `json:"price_n"`
+	PriceD             int32   `json:"price_d"`
+	Price              float64 `json:"price"`
+	BuyingAssetCode    string  `json:"buying_asset_code"`
+	BuyingAssetIssuer  string  `json:"buying_asset_issuer"`
+	BuyingAssetType    string  `json:"buying_asset_type"`
+	SellingAssetCode   string  `json:"selling_asset_code"`
+	SellingAssetIssuer string  `json:"selling_asset_issuer"`
+	SellingAssetType   string  `json:"selling_asset_type"`
+}
+
+func (o *LedgerOperation) ManageBuyOfferDetails() (ManageBuyOffer, error) {
+	op, ok := o.Operation.Body.GetManageBuyOfferOp()
+	if !ok {
+		return ManageBuyOffer{}, fmt.Errorf("could not access ManageBuyOffer info for this operation (index %d)", o.OperationIndex)
+	}
+
+	manageBuyOffer := ManageBuyOffer{
+		OfferID: int64(op.OfferId),
+		Amount:  int64(op.BuyAmount),
+		PriceN:  int32(op.Price.N),
+		PriceD:  int32(op.Price.D),
+	}
+
+	var err error
+	manageBuyOffer.Price, err = strconv.ParseFloat(op.Price.String(), 64)
+	if err != nil {
+		return ManageBuyOffer{}, err
+	}
+
+	var buyingAssetCode, buyingAssetIssuer, buyingAssetType string
+	err = op.Buying.Extract(&buyingAssetType, &buyingAssetCode, &buyingAssetIssuer)
+	if err != nil {
+		return ManageBuyOffer{}, err
+	}
+
+	manageBuyOffer.BuyingAssetCode = buyingAssetCode
+	manageBuyOffer.BuyingAssetIssuer = buyingAssetIssuer
+	manageBuyOffer.BuyingAssetType = buyingAssetType
+
+	var sellingAssetCode, sellingAssetIssuer, sellingAssetType string
+	err = op.Selling.Extract(&sellingAssetType, &sellingAssetCode, &sellingAssetIssuer)
+	if err != nil {
+		return ManageBuyOffer{}, err
+	}
+
+	manageBuyOffer.SellingAssetCode = sellingAssetCode
+	manageBuyOffer.SellingAssetIssuer = sellingAssetIssuer
+	manageBuyOffer.SellingAssetType = sellingAssetType
+
+	return manageBuyOffer, nil
+}
diff --git a/ingest/manage_data_details.go b/ingest/manage_data_details.go
new file mode 100644
index 0000000000..abf523fd6a
--- /dev/null
+++ b/ingest/manage_data_details.go
@@ -0,0 +1,28 @@
+package ingest
+
+import (
+	"encoding/base64"
+	"fmt"
+)
+
+type ManageDataDetail struct {
+	Name  string `json:"name"`
+	Value string `json:value"`
+}
+
+func (o *LedgerOperation) ManageDataDetails() (ManageDataDetail, error) {
+	op, ok := o.Operation.Body.GetManageDataOp()
+	if !ok {
+		return ManageDataDetail{}, fmt.Errorf("could not access GetManageData info for this operation (index %d)", o.OperationIndex)
+	}
+
+	manageDataDetail := ManageDataDetail{
+		Name: string(op.DataName),
+	}
+
+	if op.DataValue != nil {
+		manageDataDetail.Value = base64.StdEncoding.EncodeToString(*op.DataValue)
+	}
+
+	return manageDataDetail, nil
+}
diff --git a/ingest/manage_sell_offer_details.go b/ingest/manage_sell_offer_details.go
new file mode 100644
index 0000000000..9b33fd4b03
--- /dev/null
+++ b/ingest/manage_sell_offer_details.go
@@ -0,0 +1,63 @@
+package ingest
+
+import (
+	"fmt"
+	"strconv"
+)
+
+type ManageSellOffer struct {
+	OfferID            int64   `json:"offer_id"`
+	Amount             int64   `json:"amount"`
+	PriceN             int32   `json:"price_n"`
+	PriceD             int32   `json:"price_d"`
+	Price              float64 `json:"price"`
+	BuyingAssetCode    string  `json:"buying_asset_code"`
+	BuyingAssetIssuer  string  `json:"buying_asset_issuer"`
+	BuyingAssetType    string  `json:"buying_asset_type"`
+	SellingAssetCode   string  `json:"selling_asset_code"`
+	SellingAssetIssuer string  `json:"selling_asset_issuer"`
+	SellingAssetType   string  `json:"selling_asset_type"`
+}
+
+func (o *LedgerOperation) ManageSellOfferDetails() (ManageSellOffer, error) {
+	op, ok := o.Operation.Body.GetManageSellOfferOp()
+	if !ok {
+		return ManageSellOffer{}, fmt.Errorf("could not access ManageSellOffer info for this operation (index %d)", o.OperationIndex)
+	}
+
+	manageSellOffer := ManageSellOffer{
+		OfferID: int64(op.OfferId),
+		Amount:  int64(op.Amount),
+		PriceN:  int32(op.Price.N),
+		PriceD:  int32(op.Price.D),
+	}
+
+	var err error
+	manageSellOffer.Price, err = strconv.ParseFloat(op.Price.String(), 64)
+	if err != nil {
+		return ManageSellOffer{}, err
+	}
+
+	var buyingAssetCode, buyingAssetIssuer, buyingAssetType string
+	err = op.Buying.Extract(&buyingAssetType, &buyingAssetCode, &buyingAssetIssuer)
+	if err != nil {
+		return ManageSellOffer{}, err
+	}
+
+	manageSellOffer.BuyingAssetCode = buyingAssetCode
+	manageSellOffer.BuyingAssetIssuer = buyingAssetIssuer
+	manageSellOffer.BuyingAssetType = buyingAssetType
+
+	var sellingAssetCode, sellingAssetIssuer, sellingAssetType string
+	err = op.Selling.Extract(&sellingAssetType, &sellingAssetCode, &sellingAssetIssuer)
+	if err != nil {
+		return ManageSellOffer{}, err
+	}
+
+	manageSellOffer.SellingAssetCode = sellingAssetCode
+	manageSellOffer.SellingAssetIssuer = sellingAssetIssuer
+	manageSellOffer.SellingAssetType = sellingAssetType
+
+	return manageSellOffer, nil
+
+}
diff --git a/ingest/path_payment_strict_receive_details.go b/ingest/path_payment_strict_receive_details.go
new file mode 100644
index 0000000000..3a9eab83d4
--- /dev/null
+++ b/ingest/path_payment_strict_receive_details.go
@@ -0,0 +1,100 @@
+package ingest
+
+import (
+	"fmt"
+)
+
+type PathPaymentStrictReceiveDetail struct {
+	From              string `json:"from"`
+	FromMuxed         string `json:"from_muxed"`
+	FromMuxedID       uint64 `json:"from_muxed_id"`
+	To                string `json:"to"`
+	ToMuxed           string `json:"to_muxed"`
+	ToMuxedID         uint64 `json:"to_muxed_id"`
+	AssetCode         string `json:"asset_code"`
+	AssetIssuer       string `json:"asset_issuer"`
+	AssetType         string `json:"asset_type"`
+	Amount            int64  `json:"amount"`
+	SourceAssetCode   string `json:"source_asset_code"`
+	SourceAssetIssuer string `json:"source_asset_issuer"`
+	SourceAssetType   string `json:"source_asset_type"`
+	SourceAmount      int64  `json:"source_amount"`
+	SourceMax         int64  `json:"source_max"`
+	Path              []Path `json:"path"`
+}
+
+func (o *LedgerOperation) PathPaymentStrictReceiveDetails() (PathPaymentStrictReceiveDetail, error) {
+	op, ok := o.Operation.Body.GetPathPaymentStrictReceiveOp()
+	if !ok {
+		return PathPaymentStrictReceiveDetail{}, fmt.Errorf("could not access PathPaymentStrictReceive info for this operation (index %d)", o.OperationIndex)
+	}
+
+	pathPaymentStrictReceiveDetail := PathPaymentStrictReceiveDetail{
+		From:      o.SourceAccount(),
+		To:        op.Destination.Address(),
+		Amount:    int64(op.DestAmount),
+		SourceMax: int64(op.SendMax),
+	}
+
+	var err error
+	var fromMuxed string
+	var fromMuxedID uint64
+	fromMuxed, fromMuxedID, err = getMuxedAccountDetails(o.sourceAccountXDR())
+	if err != nil {
+		return PathPaymentStrictReceiveDetail{}, err
+	}
+
+	pathPaymentStrictReceiveDetail.FromMuxed = fromMuxed
+	pathPaymentStrictReceiveDetail.FromMuxedID = fromMuxedID
+
+	var toMuxed string
+	var toMuxedID uint64
+	toMuxed, toMuxedID, err = getMuxedAccountDetails(op.Destination)
+	if err != nil {
+		return PathPaymentStrictReceiveDetail{}, err
+	}
+
+	pathPaymentStrictReceiveDetail.ToMuxed = toMuxed
+	pathPaymentStrictReceiveDetail.ToMuxedID = toMuxedID
+
+	var assetCode, assetIssuer, assetType string
+	err = op.DestAsset.Extract(&assetType, &assetCode, &assetIssuer)
+	if err != nil {
+		return PathPaymentStrictReceiveDetail{}, err
+	}
+
+	pathPaymentStrictReceiveDetail.AssetCode = assetCode
+	pathPaymentStrictReceiveDetail.AssetIssuer = assetIssuer
+	pathPaymentStrictReceiveDetail.AssetType = assetType
+
+	var sourceAssetCode, sourceAssetIssuer, sourceAssetType string
+	err = op.SendAsset.Extract(&sourceAssetType, &sourceAssetCode, &sourceAssetIssuer)
+	if err != nil {
+		return PathPaymentStrictReceiveDetail{}, err
+	}
+
+	pathPaymentStrictReceiveDetail.SourceAssetCode = sourceAssetCode
+	pathPaymentStrictReceiveDetail.SourceAssetIssuer = sourceAssetIssuer
+	pathPaymentStrictReceiveDetail.SourceAssetType = sourceAssetType
+
+	if o.Transaction.Successful() {
+		allOperationResults, ok := o.Transaction.Result.OperationResults()
+		if !ok {
+			return PathPaymentStrictReceiveDetail{}, fmt.Errorf("could not access any results for this transaction")
+		}
+		currentOperationResult := allOperationResults[o.OperationIndex]
+		resultBody, ok := currentOperationResult.GetTr()
+		if !ok {
+			return PathPaymentStrictReceiveDetail{}, fmt.Errorf("could not access result body for this operation (index %d)", o.OperationIndex)
+		}
+		result, ok := resultBody.GetPathPaymentStrictReceiveResult()
+		if !ok {
+			return PathPaymentStrictReceiveDetail{}, fmt.Errorf("could not access PathPaymentStrictReceive result info for this operation (index %d)", o.OperationIndex)
+		}
+		pathPaymentStrictReceiveDetail.SourceAmount = int64(result.SendAmount())
+	}
+
+	pathPaymentStrictReceiveDetail.Path = o.TransformPath(op.Path)
+
+	return pathPaymentStrictReceiveDetail, nil
+}
diff --git a/ingest/path_payment_strict_send_details.go b/ingest/path_payment_strict_send_details.go
new file mode 100644
index 0000000000..6b0c28e69f
--- /dev/null
+++ b/ingest/path_payment_strict_send_details.go
@@ -0,0 +1,100 @@
+package ingest
+
+import (
+	"fmt"
+)
+
+type PathPaymentStrictSendDetail struct {
+	From              string `json:"from"`
+	FromMuxed         string `json:"from_muxed"`
+	FromMuxedID       uint64 `json:"from_muxed_id"`
+	To                string `json:"to"`
+	ToMuxed           string `json:"to_muxed"`
+	ToMuxedID         uint64 `json:"to_muxed_id"`
+	AssetCode         string `json:"asset_code"`
+	AssetIssuer       string `json:"asset_issuer"`
+	AssetType         string `json:"asset_type"`
+	Amount            int64  `json:"amount"`
+	SourceAssetCode   string `json:"source_asset_code"`
+	SourceAssetIssuer string `json:"source_asset_issuer"`
+	SourceAssetType   string `json:"source_asset_type"`
+	SourceAmount      int64  `json:"source_amount"`
+	DestinationMin    int64  `json:"destination_min"`
+	Path              []Path `json:"path"`
+}
+
+func (o *LedgerOperation) PathPaymentStrictSendDetails() (PathPaymentStrictSendDetail, error) {
+	op, ok := o.Operation.Body.GetPathPaymentStrictSendOp()
+	if !ok {
+		return PathPaymentStrictSendDetail{}, fmt.Errorf("could not access PathPaymentStrictSend info for this operation (index %d)", o.OperationIndex)
+	}
+
+	pathPaymentStrictSendDetail := PathPaymentStrictSendDetail{
+		From:           o.SourceAccount(),
+		To:             op.Destination.Address(),
+		SourceAmount:   int64(op.SendAmount),
+		DestinationMin: int64(op.DestMin),
+	}
+
+	var err error
+	var fromMuxed string
+	var fromMuxedID uint64
+	fromMuxed, fromMuxedID, err = getMuxedAccountDetails(o.sourceAccountXDR())
+	if err != nil {
+		return PathPaymentStrictSendDetail{}, err
+	}
+
+	pathPaymentStrictSendDetail.FromMuxed = fromMuxed
+	pathPaymentStrictSendDetail.FromMuxedID = fromMuxedID
+
+	var toMuxed string
+	var toMuxedID uint64
+	toMuxed, toMuxedID, err = getMuxedAccountDetails(op.Destination)
+	if err != nil {
+		return PathPaymentStrictSendDetail{}, err
+	}
+
+	pathPaymentStrictSendDetail.ToMuxed = toMuxed
+	pathPaymentStrictSendDetail.ToMuxedID = toMuxedID
+
+	var assetCode, assetIssuer, assetType string
+	err = op.DestAsset.Extract(&assetType, &assetCode, &assetIssuer)
+	if err != nil {
+		return PathPaymentStrictSendDetail{}, err
+	}
+
+	pathPaymentStrictSendDetail.AssetCode = assetCode
+	pathPaymentStrictSendDetail.AssetIssuer = assetIssuer
+	pathPaymentStrictSendDetail.AssetType = assetType
+
+	var sourceAssetCode, sourceAssetIssuer, sourceAssetType string
+	err = op.SendAsset.Extract(&sourceAssetType, &sourceAssetCode, &sourceAssetIssuer)
+	if err != nil {
+		return PathPaymentStrictSendDetail{}, err
+	}
+
+	pathPaymentStrictSendDetail.SourceAssetCode = sourceAssetCode
+	pathPaymentStrictSendDetail.SourceAssetIssuer = sourceAssetIssuer
+	pathPaymentStrictSendDetail.SourceAssetType = sourceAssetType
+
+	if o.Transaction.Successful() {
+		allOperationResults, ok := o.Transaction.Result.OperationResults()
+		if !ok {
+			return PathPaymentStrictSendDetail{}, fmt.Errorf("could not access any results for this transaction")
+		}
+		currentOperationResult := allOperationResults[o.OperationIndex]
+		resultBody, ok := currentOperationResult.GetTr()
+		if !ok {
+			return PathPaymentStrictSendDetail{}, fmt.Errorf("could not access result body for this operation (index %d)", o.OperationIndex)
+		}
+		result, ok := resultBody.GetPathPaymentStrictSendResult()
+		if !ok {
+			return PathPaymentStrictSendDetail{}, fmt.Errorf("could not access GetPathPaymentStrictSendResult result info for this operation (index %d)", o.OperationIndex)
+		}
+		pathPaymentStrictSendDetail.Amount = int64(result.DestAmount())
+	}
+
+	pathPaymentStrictSendDetail.Path = o.TransformPath(op.Path)
+
+	return pathPaymentStrictSendDetail, nil
+}
diff --git a/ingest/payment_details.go b/ingest/payment_details.go
new file mode 100644
index 0000000000..c7845ed503
--- /dev/null
+++ b/ingest/payment_details.go
@@ -0,0 +1,60 @@
+package ingest
+
+import (
+	"fmt"
+)
+
+type PaymentDetail struct {
+	From        string `json:"from"`
+	FromMuxed   string `json:"from_muxed"`
+	FromMuxedID uint64 `json:"from_muxed_id"`
+	To          string `json:"to"`
+	ToMuxed     string `json:"to_muxed"`
+	ToMuxedID   uint64 `json:"to_muxed_id"`
+	AssetCode   string `json:"asset_code"`
+	AssetIssuer string `json:"asset_issuer"`
+	AssetType   string `json:"asset_type"`
+	Amount      int64  `json:"amount"`
+}
+
+func (o *LedgerOperation) PaymentDetails() (PaymentDetail, error) {
+	op, ok := o.Operation.Body.GetPaymentOp()
+	if !ok {
+		return PaymentDetail{}, fmt.Errorf("could not access Payment info for this operation (index %d)", o.OperationIndex)
+	}
+
+	paymentDetail := PaymentDetail{
+		From:   o.SourceAccount(),
+		To:     op.Destination.Address(),
+		Amount: int64(op.Amount),
+	}
+
+	var err error
+	var fromMuxed string
+	var fromMuxedID uint64
+	fromMuxed, fromMuxedID, err = getMuxedAccountDetails(o.sourceAccountXDR())
+	if err != nil {
+		return PaymentDetail{}, err
+	}
+
+	paymentDetail.FromMuxed = fromMuxed
+	paymentDetail.FromMuxedID = fromMuxedID
+
+	var toMuxed string
+	var toMuxedID uint64
+	toMuxed, toMuxedID, err = getMuxedAccountDetails(op.Destination)
+	if err != nil {
+		return PaymentDetail{}, err
+	}
+
+	paymentDetail.ToMuxed = toMuxed
+	paymentDetail.ToMuxedID = toMuxedID
+
+	var assetCode, assetIssuer, assetType string
+	err = op.Asset.Extract(&assetType, &assetCode, &assetIssuer)
+	if err != nil {
+		return PaymentDetail{}, err
+	}
+
+	return paymentDetail, nil
+}
diff --git a/ingest/restore_footprint_details.go b/ingest/restore_footprint_details.go
new file mode 100644
index 0000000000..496308ffad
--- /dev/null
+++ b/ingest/restore_footprint_details.go
@@ -0,0 +1,36 @@
+package ingest
+
+import "fmt"
+
+type RestoreFootprintDetail struct {
+	Type             string   `json:"type"`
+	LedgerKeyHash    []string `json:"ledger_key_hash"`
+	ContractID       string   `json:"contract_id"`
+	ContractCodeHash string   `json:"contract_code_hash"`
+}
+
+func (o *LedgerOperation) RestoreFootprintDetails() (RestoreFootprintDetail, error) {
+	_, ok := o.Operation.Body.GetRestoreFootprintOp()
+	if !ok {
+		return RestoreFootprintDetail{}, fmt.Errorf("could not access RestoreFootprint info for this operation (index %d)", o.OperationIndex)
+	}
+
+	restoreFootprintDetail := RestoreFootprintDetail{
+		Type:          "restore_footprint",
+		LedgerKeyHash: o.Transaction.LedgerKeyHashFromTxEnvelope(),
+	}
+
+	var contractID string
+	contractID, ok = o.Transaction.contractIdFromTxEnvelope()
+	if ok {
+		restoreFootprintDetail.ContractID = contractID
+	}
+
+	var contractCodeHash string
+	contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+	if ok {
+		restoreFootprintDetail.ContractCodeHash = contractCodeHash
+	}
+
+	return restoreFootprintDetail, nil
+}
diff --git a/ingest/revoke_sponsorship_details.go b/ingest/revoke_sponsorship_details.go
new file mode 100644
index 0000000000..9ca5a1aefb
--- /dev/null
+++ b/ingest/revoke_sponsorship_details.go
@@ -0,0 +1,37 @@
+package ingest
+
+import (
+	"fmt"
+
+	"github.com/stellar/go/xdr"
+)
+
+type RevokeSponsorshipDetail struct {
+	SignerAccountID  string          `json:"signer_account_id"`
+	SignerKey        string          `json:"signer_key"`
+	LedgerKeyDetails LedgerKeyDetail `json:"ledger_key_detail`
+}
+
+func (o *LedgerOperation) RevokeSponsorshipDetails() (RevokeSponsorshipDetail, error) {
+	op, ok := o.Operation.Body.GetRevokeSponsorshipOp()
+	if !ok {
+		return RevokeSponsorshipDetail{}, fmt.Errorf("could not access RevokeSponsorship info for this operation (index %d)", o.OperationIndex)
+	}
+
+	var revokeSponsorshipDetail RevokeSponsorshipDetail
+
+	switch op.Type {
+	case xdr.RevokeSponsorshipTypeRevokeSponsorshipLedgerEntry:
+		ledgerKeyDetail, err := addLedgerKeyToDetails(*op.LedgerKey)
+		if err != nil {
+			return RevokeSponsorshipDetail{}, err
+		}
+
+		revokeSponsorshipDetail.LedgerKeyDetails = ledgerKeyDetail
+	case xdr.RevokeSponsorshipTypeRevokeSponsorshipSigner:
+		revokeSponsorshipDetail.SignerAccountID = op.Signer.AccountId.Address()
+		revokeSponsorshipDetail.SignerKey = op.Signer.SignerKey.Address()
+	}
+
+	return revokeSponsorshipDetail, nil
+}
diff --git a/ingest/set_options_details.go b/ingest/set_options_details.go
new file mode 100644
index 0000000000..2216e4b9b0
--- /dev/null
+++ b/ingest/set_options_details.go
@@ -0,0 +1,97 @@
+package ingest
+
+import (
+	"fmt"
+
+	"github.com/stellar/go/xdr"
+)
+
+type SetOptionsDetails struct {
+	InflationDestination string   `json:"inflation_destination"`
+	MasterKeyWeight      uint32   `json:"master_key_weight"`
+	LowThreshold         uint32   `json:"low_threshold"`
+	MediumThreshold      uint32   `json:"medium_threshold"`
+	HighThreshold        uint32   `json:"high_threshold"`
+	HomeDomain           string   `json:"home_domain"`
+	SignerKey            string   `json:"signer_key"`
+	SignerWeight         uint32   `json:"signer_weight"`
+	SetFlags             []int32  `json:"set_flags"`
+	SetFlagsString       []string `json:"set_flags_string"`
+	ClearFlags           []int32  `json:"clear_flags"`
+	ClearFlagsString     []string `json:"clear_flags_string"`
+}
+
+func (o *LedgerOperation) SetOptionsDetails() (SetOptionsDetails, error) {
+	op, ok := o.Operation.Body.GetSetOptionsOp()
+	if !ok {
+		return SetOptionsDetails{}, fmt.Errorf("could not access GetSetOptions info for this operation (index %d)", o.OperationIndex)
+	}
+
+	var setOptionsDetail SetOptionsDetails
+
+	if op.InflationDest != nil {
+		setOptionsDetail.InflationDestination = op.InflationDest.Address()
+	}
+
+	if op.SetFlags != nil && *op.SetFlags > 0 {
+		setOptionsDetail.SetFlags, setOptionsDetail.SetFlagsString = addOperationFlagToOperationDetails(uint32(*op.SetFlags))
+	}
+
+	if op.ClearFlags != nil && *op.ClearFlags > 0 {
+		setOptionsDetail.ClearFlags, setOptionsDetail.ClearFlagsString = addOperationFlagToOperationDetails(uint32(*op.ClearFlags))
+	}
+
+	if op.MasterWeight != nil {
+		setOptionsDetail.MasterKeyWeight = uint32(*op.MasterWeight)
+	}
+
+	if op.LowThreshold != nil {
+		setOptionsDetail.LowThreshold = uint32(*op.LowThreshold)
+	}
+
+	if op.MedThreshold != nil {
+		setOptionsDetail.MediumThreshold = uint32(*op.MedThreshold)
+	}
+
+	if op.HighThreshold != nil {
+		setOptionsDetail.HighThreshold = uint32(*op.HighThreshold)
+	}
+
+	if op.HomeDomain != nil {
+		setOptionsDetail.HomeDomain = string(*op.HomeDomain)
+	}
+
+	if op.Signer != nil {
+		setOptionsDetail.SignerKey = op.Signer.Key.Address()
+		setOptionsDetail.SignerWeight = uint32(op.Signer.Weight)
+	}
+
+	return setOptionsDetail, nil
+}
+
+func addOperationFlagToOperationDetails(flag uint32) ([]int32, []string) {
+	intFlags := make([]int32, 0)
+	stringFlags := make([]string, 0)
+
+	if (int64(flag) & int64(xdr.AccountFlagsAuthRequiredFlag)) > 0 {
+		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthRequiredFlag))
+		stringFlags = append(stringFlags, "auth_required")
+	}
+
+	if (int64(flag) & int64(xdr.AccountFlagsAuthRevocableFlag)) > 0 {
+		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthRevocableFlag))
+		stringFlags = append(stringFlags, "auth_revocable")
+	}
+
+	if (int64(flag) & int64(xdr.AccountFlagsAuthImmutableFlag)) > 0 {
+		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthImmutableFlag))
+		stringFlags = append(stringFlags, "auth_immutable")
+	}
+
+	if (int64(flag) & int64(xdr.AccountFlagsAuthClawbackEnabledFlag)) > 0 {
+		intFlags = append(intFlags, int32(xdr.AccountFlagsAuthClawbackEnabledFlag))
+		stringFlags = append(stringFlags, "auth_clawback_enabled")
+	}
+
+	return intFlags, stringFlags
+}
diff --git a/ingest/set_trustline_flags_details.go b/ingest/set_trustline_flags_details.go
new file mode 100644
index 0000000000..6a71c4c22a
--- /dev/null
+++ b/ingest/set_trustline_flags_details.go
@@ -0,0 +1,73 @@
+package ingest
+
+import (
+	"fmt"
+
+	"github.com/stellar/go/xdr"
+)
+
+type SetTrustlineFlagsDetail struct {
+	AssetCode        string   `json:"asset_code"`
+	AssetIssuer      string   `json:"asset_issuer"`
+	AssetType        string   `json:"asset_type"`
+	Trustor          string   `json:"trustor"`
+	SetFlags         []int32  `json:"set_flags"`
+	SetFlagsString   []string `json:"set_flags_string"`
+	ClearFlags       []int32  `json:"clear_flags"`
+	ClearFlagsString []string `json:"clear_flags_string"`
+}
+
+func (o *LedgerOperation) SetTrustlineFlagsDetails() (SetTrustlineFlagsDetail, error) {
+	op, ok := o.Operation.Body.GetSetTrustLineFlagsOp()
+	if !ok {
+		return SetTrustlineFlagsDetail{}, fmt.Errorf("could not access SetTrustLineFlags info for this operation (index %d)", o.OperationIndex)
+	}
+
+	setTrustLineFlagsDetail := SetTrustlineFlagsDetail{
+		Trustor: op.Trustor.Address(),
+	}
+
+	var err error
+	var assetCode, assetIssuer, assetType string
+	err = op.Asset.Extract(&assetType, &assetCode, &assetIssuer)
+	if err != nil {
+		return SetTrustlineFlagsDetail{}, err
+	}
+
+	setTrustLineFlagsDetail.AssetCode = assetCode
+	setTrustLineFlagsDetail.AssetIssuer = assetIssuer
+	setTrustLineFlagsDetail.AssetType = assetType
+
+	if op.SetFlags > 0 {
+		setTrustLineFlagsDetail.SetFlags, setTrustLineFlagsDetail.SetFlagsString = getTrustLineFlagToDetails(xdr.TrustLineFlags(op.SetFlags))
+	}
+	if op.ClearFlags > 0 {
+		setTrustLineFlagsDetail.ClearFlags, setTrustLineFlagsDetail.ClearFlagsString = getTrustLineFlagToDetails(xdr.TrustLineFlags(op.ClearFlags))
+	}
+
+	return setTrustLineFlagsDetail, nil
+}
+
+func getTrustLineFlagToDetails(f xdr.TrustLineFlags) ([]int32, []string) {
+	var (
+		n []int32
+		s []string
+	)
+
+	if f.IsAuthorized() {
+		n = append(n, int32(xdr.TrustLineFlagsAuthorizedFlag))
+		s = append(s, "authorized")
+	}
+
+	if f.IsAuthorizedToMaintainLiabilitiesFlag() {
+		n = append(n, int32(xdr.TrustLineFlagsAuthorizedToMaintainLiabilitiesFlag))
+		s = append(s, "authorized_to_maintain_liabilities")
+	}
+
+	if f.IsClawbackEnabledFlag() {
+		n = append(n, int32(xdr.TrustLineFlagsTrustlineClawbackEnabledFlag))
+		s = append(s, "clawback_enabled")
+	}
+
+	return n, s
+}

From b7c0737e0b1af37f0cf5694609837c76eb362207 Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Mon, 3 Feb 2025 17:55:32 -0500
Subject: [PATCH 05/12] Update marshalbase64

---
 ingest/ledger_transaction.go |  6 +++---
 xdr/hash.go                  | 12 ------------
 2 files changed, 3 insertions(+), 15 deletions(-)

diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go
index d32b403743..bbfa86da22 100644
--- a/ingest/ledger_transaction.go
+++ b/ingest/ledger_transaction.go
@@ -632,7 +632,7 @@ func (t *LedgerTransaction) LedgerKeyHashFromTxEnvelope() []string {
 	}
 
 	for _, ledgerKey := range v1Envelope.Tx.Ext.SorobanData.Resources.Footprint.ReadOnly {
-		ledgerKeyBase64, err := ledgerKey.MarshalBinaryBase64()
+		ledgerKeyBase64, err := xdr.MarshalBase64(ledgerKey)
 		if err != nil {
 			panic(err)
 		}
@@ -642,7 +642,7 @@ func (t *LedgerTransaction) LedgerKeyHashFromTxEnvelope() []string {
 	}
 
 	for _, ledgerKey := range v1Envelope.Tx.Ext.SorobanData.Resources.Footprint.ReadWrite {
-		ledgerKeyBase64, err := ledgerKey.MarshalBinaryBase64()
+		ledgerKeyBase64, err := xdr.MarshalBase64(ledgerKey)
 		if err != nil {
 			panic(err)
 		}
@@ -688,7 +688,7 @@ func (t *LedgerTransaction) contractCodeFromContractData(ledgerKey xdr.LedgerKey
 		return "", false
 	}
 
-	codeHash, err := contractCode.Hash.MarshalBinaryBase64()
+	codeHash, err := xdr.MarshalBase64(contractCode.Hash)
 	if err != nil {
 		panic(err)
 	}
diff --git a/xdr/hash.go b/xdr/hash.go
index c0107163b6..084520d082 100644
--- a/xdr/hash.go
+++ b/xdr/hash.go
@@ -1,7 +1,6 @@
 package xdr
 
 import (
-	"encoding/base64"
 	"encoding/hex"
 )
 
@@ -20,14 +19,3 @@ func (s Hash) Equals(o Hash) bool {
 	}
 	return true
 }
-
-// MarshalBinaryBase64 marshals XDR into a binary form and then encodes it
-// using base64.
-func (h Hash) MarshalBinaryBase64() (string, error) {
-	b, err := h.MarshalBinary()
-	if err != nil {
-		return "", err
-	}
-
-	return base64.StdEncoding.EncodeToString(b), nil
-}

From a3846f2c97a1851f98b66fd8f141baf4ed487896 Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Mon, 3 Feb 2025 18:33:05 -0500
Subject: [PATCH 06/12] Fix typos

---
 ingest/manage_data_details.go        | 2 +-
 ingest/revoke_sponsorship_details.go | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/ingest/manage_data_details.go b/ingest/manage_data_details.go
index abf523fd6a..ce52817130 100644
--- a/ingest/manage_data_details.go
+++ b/ingest/manage_data_details.go
@@ -7,7 +7,7 @@ import (
 
 type ManageDataDetail struct {
 	Name  string `json:"name"`
-	Value string `json:value"`
+	Value string `json:"value"`
 }
 
 func (o *LedgerOperation) ManageDataDetails() (ManageDataDetail, error) {
diff --git a/ingest/revoke_sponsorship_details.go b/ingest/revoke_sponsorship_details.go
index 9ca5a1aefb..654d5f128e 100644
--- a/ingest/revoke_sponsorship_details.go
+++ b/ingest/revoke_sponsorship_details.go
@@ -9,7 +9,7 @@ import (
 type RevokeSponsorshipDetail struct {
 	SignerAccountID  string          `json:"signer_account_id"`
 	SignerKey        string          `json:"signer_key"`
-	LedgerKeyDetails LedgerKeyDetail `json:"ledger_key_detail`
+	LedgerKeyDetails LedgerKeyDetail `json:"ledger_key_detail"`
 }
 
 func (o *LedgerOperation) RevokeSponsorshipDetails() (RevokeSponsorshipDetail, error) {

From fc6b854ce66fe08c04a75b59158ac0ce7003a8c6 Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Mon, 3 Feb 2025 18:49:55 -0500
Subject: [PATCH 07/12] fix ci/cd

---
 ingest/ledger_operation.go                | 3 ++-
 ingest/liquidity_pool_deposit_details.go  | 4 +++-
 ingest/liquidity_pool_withdraw_details.go | 4 +++-
 3 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/ingest/ledger_operation.go b/ingest/ledger_operation.go
index 77b831448d..3b6d4e49f1 100644
--- a/ingest/ledger_operation.go
+++ b/ingest/ledger_operation.go
@@ -69,13 +69,14 @@ func (o *LedgerOperation) OperationTraceCode() (string, error) {
 	var operationTraceCode string
 	var operationResults []xdr.OperationResult
 	var ok bool
+	var err error
 
 	operationResults, ok = o.Transaction.Result.Result.OperationResults()
 	if ok {
 		var operationResultTr xdr.OperationResultTr
 		operationResultTr, ok = operationResults[o.OperationIndex].GetTr()
 		if ok {
-			operationTraceCode, err := operationResultTr.MapOperationResultTr()
+			operationTraceCode, err = operationResultTr.MapOperationResultTr()
 			if err != nil {
 				return "", err
 			}
diff --git a/ingest/liquidity_pool_deposit_details.go b/ingest/liquidity_pool_deposit_details.go
index 92f4322a98..39e65288c5 100644
--- a/ingest/liquidity_pool_deposit_details.go
+++ b/ingest/liquidity_pool_deposit_details.go
@@ -56,10 +56,12 @@ func (o *LedgerOperation) LiquidityPoolDepositDetails() (LiquidityPoolDepositDet
 		assetA, assetB         xdr.Asset
 		depositedA, depositedB xdr.Int64
 		sharesReceived         xdr.Int64
+		lp                     *xdr.LiquidityPoolEntry
+		delta                  *LiquidityPoolDelta
 	)
 	if o.Transaction.Successful() {
 		// we will use the defaults (omitted asset and 0 amounts) if the transaction failed
-		lp, delta, err := o.getLiquidityPoolAndProductDelta(&op.LiquidityPoolId)
+		lp, delta, err = o.getLiquidityPoolAndProductDelta(&op.LiquidityPoolId)
 		if err != nil {
 			return LiquidityPoolDepositDetail{}, err
 		}
diff --git a/ingest/liquidity_pool_withdraw_details.go b/ingest/liquidity_pool_withdraw_details.go
index c250494e3d..9c4de93b3b 100644
--- a/ingest/liquidity_pool_withdraw_details.go
+++ b/ingest/liquidity_pool_withdraw_details.go
@@ -45,10 +45,12 @@ func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (LiquidityPoolWithdrawD
 	var (
 		assetA, assetB       xdr.Asset
 		receivedA, receivedB xdr.Int64
+		lp                   *xdr.LiquidityPoolEntry
+		delta                *LiquidityPoolDelta
 	)
 	if o.Transaction.Successful() {
 		// we will use the defaults (omitted asset and 0 amounts) if the transaction failed
-		lp, delta, err := o.getLiquidityPoolAndProductDelta(&op.LiquidityPoolId)
+		lp, delta, err = o.getLiquidityPoolAndProductDelta(&op.LiquidityPoolId)
 		if err != nil {
 			return LiquidityPoolWithdrawDetail{}, err
 		}

From 875c995d4cedd78b0607beacc6d1e25fb4e0a469 Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Mon, 3 Feb 2025 20:30:24 -0500
Subject: [PATCH 08/12] fix tests

---
 ingest/allow_trust_details.go             |   2 +-
 ingest/bump_sequence_details.go           |   4 +-
 ingest/clawback_details.go                |   1 +
 ingest/ledger_operation_test.go           | 538 +++++++++++-----------
 ingest/liquidity_pool_withdraw_details.go |   2 +-
 ingest/payment_details.go                 |   4 +
 6 files changed, 269 insertions(+), 282 deletions(-)

diff --git a/ingest/allow_trust_details.go b/ingest/allow_trust_details.go
index df06278738..f2cbed5cb8 100644
--- a/ingest/allow_trust_details.go
+++ b/ingest/allow_trust_details.go
@@ -28,7 +28,7 @@ func (o *LedgerOperation) AllowTrustDetails() (AllowTrustDetail, error) {
 	allowTrustDetail := AllowTrustDetail{
 		Trustor:                        op.Trustor.Address(),
 		Trustee:                        o.SourceAccount(),
-		Authorize:                      xdr.TrustLineFlags(op.Authorize).IsAuthorizedToMaintainLiabilitiesFlag(),
+		Authorize:                      xdr.TrustLineFlags(op.Authorize).IsAuthorized(),
 		AuthorizeToMaintainLiabilities: xdr.TrustLineFlags(op.Authorize).IsAuthorizedToMaintainLiabilitiesFlag(),
 		ClawbackEnabled:                xdr.TrustLineFlags(op.Authorize).IsClawbackEnabledFlag(),
 	}
diff --git a/ingest/bump_sequence_details.go b/ingest/bump_sequence_details.go
index c3e3d2e86e..b2e16a895c 100644
--- a/ingest/bump_sequence_details.go
+++ b/ingest/bump_sequence_details.go
@@ -3,7 +3,7 @@ package ingest
 import "fmt"
 
 type BumpSequenceDetails struct {
-	BumpTo string `json:"bump_to"`
+	BumpTo int64 `json:"bump_to"`
 }
 
 func (o *LedgerOperation) BumpSequenceDetails() (BumpSequenceDetails, error) {
@@ -13,6 +13,6 @@ func (o *LedgerOperation) BumpSequenceDetails() (BumpSequenceDetails, error) {
 	}
 
 	return BumpSequenceDetails{
-		BumpTo: fmt.Sprintf("%d", op.BumpTo),
+		BumpTo: int64(op.BumpTo),
 	}, nil
 }
diff --git a/ingest/clawback_details.go b/ingest/clawback_details.go
index f9dc0f6fd4..f91dda8487 100644
--- a/ingest/clawback_details.go
+++ b/ingest/clawback_details.go
@@ -20,6 +20,7 @@ func (o *LedgerOperation) ClawbackDetails() (ClawbackDetail, error) {
 
 	clawbackDetail := ClawbackDetail{
 		Amount: int64(op.Amount),
+		From:   op.From.Address(),
 	}
 
 	var err error
diff --git a/ingest/ledger_operation_test.go b/ingest/ledger_operation_test.go
index d7127104dc..f70a8534c7 100644
--- a/ingest/ledger_operation_test.go
+++ b/ingest/ledger_operation_test.go
@@ -488,7 +488,7 @@ func transactionTestInput() *LedgerTransaction {
 
 type testOutput struct {
 	err    error
-	result map[string]interface{}
+	result interface{}
 }
 
 func operationTestInput() []xdr.Operation {
@@ -1086,218 +1086,194 @@ func resultTestOutput() []testOutput {
 	output := []testOutput{
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"account":          "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"funder":           "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
-				"funder_muxed":     "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
-				"funder_muxed_id":  uint64(123),
-				"starting_balance": int64(25000000)},
+			result: CreateAccountDetail{
+				Account:         "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				Funder:          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				FunderMuxed:     "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				FunderMuxedID:   uint64(123),
+				StartingBalance: int64(25000000)},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"amount":        int64(350000000),
-				"asset_code":    "USDT",
-				"asset_id":      int64(-8205667356306085451),
-				"asset_issuer":  "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"asset_type":    "credit_alphanum4",
-				"from":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
-				"from_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
-				"from_muxed_id": uint64(123),
-				"to":            "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+			result: PaymentDetail{
+				Amount:      int64(350000000),
+				AssetCode:   "USDT",
+				AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				AssetType:   "credit_alphanum4",
+				From:        "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				FromMuxed:   "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				FromMuxedID: uint64(123),
+				To:          "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"amount":        int64(350000000),
-				"asset_id":      int64(-5706705804583548011),
-				"asset_type":    "native",
-				"from":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
-				"from_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
-				"from_muxed_id": uint64(123),
-				"to":            "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+			result: PaymentDetail{
+				Amount:      int64(350000000),
+				AssetType:   "native",
+				From:        "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				FromMuxed:   "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				FromMuxedID: uint64(123),
+				To:          "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"amount":     int64(8951495900),
-				"asset_id":   int64(-5706705804583548011),
-				"asset_type": "native",
-				"from":       "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN",
-				"path": []Path{
+			result: PathPaymentStrictReceiveDetail{
+				Amount:    int64(8951495900),
+				AssetType: "native",
+				From:      "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN",
+				Path: []Path{
 					{
 						AssetCode:   "USDT",
 						AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 						AssetType:   "credit_alphanum4",
 					},
 				},
-				"source_amount":     int64(0),
-				"source_asset_id":   int64(-5706705804583548011),
-				"source_asset_type": "native",
-				"source_max":        int64(8951495900),
-				"to":                "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+				SourceAmount:    int64(0),
+				SourceAssetType: "native",
+				SourceMax:       int64(8951495900),
+				To:              "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"amount":            int64(765860000),
-				"buying_asset_id":   int64(-5706705804583548011),
-				"buying_asset_type": "native",
-				"offer_id":          int64(0),
-				"price":             0.514092,
-				"price_r": Price{
-					Numerator:   128523,
-					Denominator: 250000,
-				},
-				"selling_asset_code":   "USDT",
-				"selling_asset_id":     int64(-8205667356306085451),
-				"selling_asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"selling_asset_type":   "credit_alphanum4"},
+			result: ManageSellOffer{
+				Amount:             int64(765860000),
+				BuyingAssetType:    "native",
+				OfferID:            int64(0),
+				Price:              0.514092,
+				PriceN:             128523,
+				PriceD:             250000,
+				SellingAssetCode:   "USDT",
+				SellingAssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				SellingAssetType:   "credit_alphanum4"},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"amount":              int64(631595000),
-				"buying_asset_code":   "USDT",
-				"buying_asset_id":     int64(-8205667356306085451),
-				"buying_asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"buying_asset_type":   "credit_alphanum4",
-				"price":               0.0791606,
-				"price_r": Price{
-					Numerator:   99583200,
-					Denominator: 1257990000,
-				},
-				"selling_asset_id":   int64(-5706705804583548011),
-				"selling_asset_type": "native"},
+			result: CreatePassiveSellOfferDetail{
+				Amount:            int64(631595000),
+				BuyingAssetCode:   "USDT",
+				BuyingAssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				BuyingAssetType:   "credit_alphanum4",
+				Price:             0.0791606,
+				PriceN:            99583200,
+				PriceD:            1257990000,
+				SellingAssetType:  "native"},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"clear_flags": []int32{1,
-					2},
-				"clear_flags_s": []string{"auth_required",
-					"auth_revocable"},
-				"high_threshold":    uint32(5),
-				"home_domain":       "2019=DRA;n-test",
-				"inflation_dest":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"low_threshold":     uint32(1),
-				"master_key_weight": uint32(3),
-				"med_threshold":     uint32(3),
-				"set_flags":         []int32{4},
-				"set_flags_s":       []string{"auth_immutable"},
-				"signer_key":        "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
-				"signer_weight":     uint32(1)},
+			result: SetOptionsDetails{
+				ClearFlags:           []int32{1, 2},
+				ClearFlagsString:     []string{"auth_required", "auth_revocable"},
+				HighThreshold:        uint32(5),
+				HomeDomain:           "2019=DRA;n-test",
+				InflationDestination: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				LowThreshold:         uint32(1),
+				MasterKeyWeight:      uint32(3),
+				MediumThreshold:      uint32(3),
+				SetFlags:             []int32{4},
+				SetFlagsString:       []string{"auth_immutable"},
+				SignerKey:            "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
+				SignerWeight:         uint32(1)},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"asset_code":       "USSD",
-				"asset_id":         int64(6690054458235693884),
-				"asset_issuer":     "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"asset_type":       "credit_alphanum4",
-				"limit":            int64(500000000000000000),
-				"trustee":          "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"trustor":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
-				"trustor_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
-				"trustor_muxed_id": uint64(123)},
+			result: ChangeTrustDetail{
+				AssetCode:      "USSD",
+				AssetIssuer:    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				AssetType:      "credit_alphanum4",
+				Limit:          int64(500000000000000000),
+				Trustee:        "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				Trustor:        "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				TrustorMuxed:   "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				TrustorMuxedID: uint64(123)},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"asset_type":        "liquidity_pool_shares",
-				"limit":             int64(500000000000000000),
-				"liquidity_pool_id": "1c261d6c75930204a73b480c3020ab525e9be48ce93de6194cf69fb06f07452d",
-				"trustor":           "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
-				"trustor_muxed":     "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
-				"trustor_muxed_id":  uint64(123)},
+			result: ChangeTrustDetail{
+				AssetType:       "liquidity_pool_shares",
+				Limit:           int64(500000000000000000),
+				LiquidityPoolID: "HCYdbHWTAgSnO0gMMCCrUl6b5IzpPeYZTPafsG8HRS0=",
+				Trustor:         "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				TrustorMuxed:    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				TrustorMuxedID:  uint64(123)},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"asset_code":       "USDT",
-				"asset_id":         int64(8181787832768848499),
-				"asset_issuer":     "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
-				"asset_type":       "credit_alphanum4",
-				"authorize":        true,
-				"trustee":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
-				"trustee_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
-				"trustee_muxed_id": uint64(123),
-				"trustor":          "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+			result: AllowTrustDetail{
+				AssetCode:      "USDT",
+				AssetIssuer:    "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				AssetType:      "credit_alphanum4",
+				Authorize:      true,
+				Trustee:        "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				TrusteeMuxed:   "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				TrusteeMuxedID: uint64(123),
+				Trustor:        "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"account":          "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
-				"account_muxed":    "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
-				"account_muxed_id": uint64(123),
-				"into":             "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+			result: AccountMergeDetail{
+				Account:        "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				AccountMuxed:   "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				AccountMuxedID: uint64(123),
+				Into:           "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
 		},
 		{
 			err:    nil,
-			result: map[string]interface{}{},
+			result: InflationDetail{},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"name":  "test",
-				"value": "dmFsdWU=",
+			result: ManageDataDetail{
+				Name:  "test",
+				Value: "dmFsdWU=",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"bump_to": "100",
+			result: BumpSequenceDetails{
+				BumpTo: int64(100),
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"amount":            int64(7654501001),
-				"buying_asset_id":   int64(-5706705804583548011),
-				"buying_asset_type": "native",
-				"offer_id":          int64(100),
-				"price":             0.3496823,
-				"price_r": Price{
-					Numerator:   635863285,
-					Denominator: 1818402817,
-				},
-				"selling_asset_code":   "USDT",
-				"selling_asset_id":     int64(-8205667356306085451),
-				"selling_asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"selling_asset_type":   "credit_alphanum4"},
+			result: ManageBuyOffer{
+				Amount:             int64(7654501001),
+				BuyingAssetType:    "native",
+				OfferID:            int64(100),
+				Price:              0.3496823,
+				PriceN:             635863285,
+				PriceD:             1818402817,
+				SellingAssetCode:   "USDT",
+				SellingAssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				SellingAssetType:   "credit_alphanum4"},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"amount":          int64(640000000),
-				"asset_id":        int64(-5706705804583548011),
-				"asset_type":      "native",
-				"destination_min": "428.0460538",
-				"from":            "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
-				"from_muxed":      "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
-				"from_muxed_id":   uint64(123),
-				"path": []Path{
+			result: PathPaymentStrictSendDetail{
+				Amount:         int64(640000000),
+				AssetType:      "native",
+				DestinationMin: 4280460538,
+				From:           "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK",
+				FromMuxed:      "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I",
+				FromMuxedID:    uint64(123),
+				Path: []Path{
 					{
 						AssetCode:   "USDT",
 						AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 						AssetType:   "credit_alphanum4",
 					},
 				},
-				"source_amount":     int64(1598182),
-				"source_asset_id":   int64(-5706705804583548011),
-				"source_asset_type": "native",
-				"to":                "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
+				SourceAmount:    int64(1598182),
+				SourceAssetType: "native",
+				To:              "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"amount":       int64(1234567890000),
-				"asset":        "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"asset_code":   "USDT",
-				"asset_id":     int64(-8205667356306085451),
-				"asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"asset_type":   "credit_alphanum4",
-				"claimants": []Claimant{
+			result: CreateClaimableBalanceDetail{
+				Amount:      int64(1234567890000),
+				AssetCode:   "USDT",
+				AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				AssetType:   "credit_alphanum4",
+				Claimants: []Claimant{
 					{
 						Destination: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 						Predicate: xdr.ClaimPredicate{
@@ -1314,143 +1290,153 @@ func resultTestOutput() []testOutput {
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000",
-				"claimant":   "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN",
+			result: ClaimClaimableBalanceDetail{
+				BalanceID: "AAAAAAECAwQFBgcICQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+				Claimant:  "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"sponsored_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+			result: BeginSponsoringFutureReservesDetail{
+				SponsoredID: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"signer_account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"signer_key":        "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
+			result: RevokeSponsorshipDetail{
+				SignerAccountID: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				SignerKey:       "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+			result: RevokeSponsorshipDetail{
+				LedgerKeyDetails: LedgerKeyDetail{
+					AccountID: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				},
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"claimable_balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000",
+			result: RevokeSponsorshipDetail{
+				LedgerKeyDetails: LedgerKeyDetail{
+					ClaimableBalanceID: "000000000102030405060708090000000000000000000000000000000000000000000000",
+				},
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"data_account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"data_name":       "test",
+			result: RevokeSponsorshipDetail{
+				LedgerKeyDetails: LedgerKeyDetail{
+					DataAccountID: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+					DataName:      "test",
+				},
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"offer_id": int64(100),
+			result: RevokeSponsorshipDetail{
+				LedgerKeyDetails: LedgerKeyDetail{
+					OfferID: int64(100),
+				},
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"trustline_account_id": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"trustline_asset":      "USTT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+			result: RevokeSponsorshipDetail{
+				LedgerKeyDetails: LedgerKeyDetail{
+					TrustlineAccountID:   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+					TrustlineAssetCode:   "USTT",
+					TrustlineAssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+					TrustlineAssetType:   "credit_alphanum4",
+				},
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"liquidity_pool_id": "0102030405060708090000000000000000000000000000000000000000000000",
+			result: RevokeSponsorshipDetail{
+				LedgerKeyDetails: LedgerKeyDetail{
+					LiquidityPoolID: "AQIDBAUGBwgJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
+				},
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"amount":       int64(1598182),
-				"asset_code":   "USDT",
-				"asset_id":     int64(-8205667356306085451),
-				"asset_issuer": "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"asset_type":   "credit_alphanum4",
-				"from":         "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+			result: ClawbackDetail{
+				Amount:      int64(1598182),
+				AssetCode:   "USDT",
+				AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				AssetType:   "credit_alphanum4",
+				From:        "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"balance_id": "000000000102030405060708090000000000000000000000000000000000000000000000",
+			result: ClawbackClaimableBalanceDetail{
+				BalanceID: "AAAAAAECAwQFBgcICQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"asset_code":    "USDT",
-				"asset_id":      int64(-8205667356306085451),
-				"asset_issuer":  "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"asset_type":    "credit_alphanum4",
-				"clear_flags":   []int32{1, 2},
-				"clear_flags_s": []string{"authorized", "authorized_to_maintain_liabilities"},
-				"set_flags":     []int32{4},
-				"set_flags_s":   []string{"clawback_enabled"},
-				"trustor":       "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+			result: SetTrustlineFlagsDetail{
+				AssetCode:        "USDT",
+				AssetIssuer:      "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				AssetType:        "credit_alphanum4",
+				ClearFlags:       []int32{1, 2},
+				ClearFlagsString: []string{"authorized", "authorized_to_maintain_liabilities"},
+				SetFlags:         []int32{4},
+				SetFlagsString:   []string{"clawback_enabled"},
+				Trustor:          "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"liquidity_pool_id":        "0102030405060708090000000000000000000000000000000000000000000000",
-				"max_price":                1e+06,
-				"max_price_r":              Price{Numerator: 1000000, Denominator: 1},
-				"min_price":                1e-06,
-				"min_price_r":              Price{Numerator: 1, Denominator: 1000000},
-				"reserve_a_asset_code":     "USDT",
-				"reserve_a_asset_id":       int64(-8205667356306085451),
-				"reserve_a_asset_issuer":   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"reserve_a_asset_type":     "credit_alphanum4",
-				"reserve_a_deposit_amount": 1e-07,
-				"reserve_a_max_amount":     int64(1000),
-				"reserve_b_asset_code":     "USDT",
-				"reserve_b_asset_id":       int64(-8205667356306085451),
-				"reserve_b_asset_issuer":   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"reserve_b_asset_type":     "credit_alphanum4",
-				"reserve_b_deposit_amount": 1e-07,
-				"reserve_b_max_amount":     int64(100),
-				"shares_received":          1e-07,
+			result: LiquidityPoolDepositDetail{
+				LiquidityPoolID:       "AQIDBAUGBwgJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
+				MaxPrice:              1e+06,
+				MaxPriceN:             1000000,
+				MaxPriceD:             1,
+				MinPrice:              1e-06,
+				MinPriceN:             1,
+				MinPriceD:             1000000,
+				ReserveAAssetCode:     "USDT",
+				ReserveAAssetIssuer:   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				ReserveAAssetType:     "credit_alphanum4",
+				ReserveADepositAmount: int64(1),
+				ReserveAMaxAmount:     int64(1000),
+				ReserveBAssetCode:     "USDT",
+				ReserveBAssetIssuer:   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				ReserveBAssetType:     "credit_alphanum4",
+				ReserveBDepositAmount: 1,
+				ReserveBMaxAmount:     int64(100),
+				SharesReceived:        1,
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"liquidity_pool_id":         "0102030405060708090000000000000000000000000000000000000000000000",
-				"reserve_a_asset_code":      "USDT",
-				"reserve_a_asset_id":        int64(-8205667356306085451),
-				"reserve_a_asset_issuer":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"reserve_a_asset_type":      "credit_alphanum4",
-				"reserve_a_min_amount":      int64(1),
-				"reserve_a_withdraw_amount": int64(-1),
-				"reserve_b_asset_code":      "USDT",
-				"reserve_b_asset_id":        int64(-8205667356306085451),
-				"reserve_b_asset_issuer":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"reserve_b_asset_type":      "credit_alphanum4",
-				"reserve_b_min_amount":      int64(1),
-				"reserve_b_withdraw_amount": int64(-1),
-				"shares":                    int64(4),
+			result: LiquidityPoolWithdrawDetail{
+				LiquidityPoolID:        "AQIDBAUGBwgJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
+				ReserveAAssetCode:      "USDT",
+				ReserveAAssetIssuer:    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				ReserveAAssetType:      "credit_alphanum4",
+				ReserveAMinAmount:      int64(1),
+				ReserveAWithdrawAmount: int64(-1),
+				ReserveBAssetCode:      "USDT",
+				ReserveBAssetIssuer:    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				ReserveBAssetType:      "credit_alphanum4",
+				ReserveBMinAmount:      int64(1),
+				ReserveBWithdrawAmount: int64(-1),
+				Shares:                 int64(4),
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"asset_balance_changes": []map[string]interface{}{},
-				"contract_id":           "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37",
-				"function":              "HostFunctionTypeHostFunctionTypeInvokeContract",
-				"ledger_key_hash":       []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
-				"parameters": []map[string]string{
+			result: InvokeHostFunctionDetail{
+				AssetBalanceChanges: []BalanceChangeDetail{},
+				ContractID:          "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37",
+				Function:            "HostFunctionTypeHostFunctionTypeInvokeContract",
+				LedgerKeyHash:       []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				Parameters: []map[string]string{
 					{
 						"type":  "Address",
 						"value": "AAAAEgAAAAESNFZ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
@@ -1460,7 +1446,7 @@ func resultTestOutput() []testOutput {
 						"value": "AAAADwAAAAR0ZXN0",
 					},
 				},
-				"parameters_decoded": []map[string]string{
+				ParametersDecoded: []map[string]string{
 					{
 						"type":  "Address",
 						"value": "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37",
@@ -1470,85 +1456,81 @@ func resultTestOutput() []testOutput {
 						"value": "test",
 					},
 				},
-				"type": "invoke_contract",
+				Type: "invoke_contract",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"address":         "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37",
-				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
-				"from":            "address",
-				"function":        "HostFunctionTypeHostFunctionTypeCreateContract",
-				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
-				"type":            "create_contract",
+			result: InvokeHostFunctionDetail{
+				Address:       "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37",
+				ContractID:    "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
+				From:          "address",
+				Function:      "HostFunctionTypeHostFunctionTypeCreateContract",
+				LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				Type:          "create_contract",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"asset":           "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"asset_code":      "USDT",
-				"asset_id":        int64(-8205667356306085451),
-				"asset_issuer":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"asset_type":      "credit_alphanum4",
-				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
-				"from":            "asset",
-				"function":        "HostFunctionTypeHostFunctionTypeCreateContract",
-				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
-				"type":            "create_contract",
+			result: InvokeHostFunctionDetail{
+				AssetCode:     "USDT",
+				AssetIssuer:   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				AssetType:     "credit_alphanum4",
+				ContractID:    "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
+				From:          "asset",
+				Function:      "HostFunctionTypeHostFunctionTypeCreateContract",
+				LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				Type:          "create_contract",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"asset":           "USDT:GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"asset_code":      "USDT",
-				"asset_id":        int64(-8205667356306085451),
-				"asset_issuer":    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				"asset_type":      "credit_alphanum4",
-				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
-				"from":            "asset",
-				"function":        "HostFunctionTypeHostFunctionTypeCreateContractV2",
-				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
-				"parameters": []map[string]string{
+			result: InvokeHostFunctionDetail{
+				AssetCode:     "USDT",
+				AssetIssuer:   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+				AssetType:     "credit_alphanum4",
+				ContractID:    "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
+				From:          "asset",
+				Function:      "HostFunctionTypeHostFunctionTypeCreateContractV2",
+				LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				Parameters: []map[string]string{
 					{
 						"type":  "B",
 						"value": "AAAAAAAAAAE=",
 					},
 				},
-				"parameters_decoded": []map[string]string{
+				ParametersDecoded: []map[string]string{
 					{
 						"type":  "B",
 						"value": "true",
 					},
 				},
-				"type": "create_contract_v2",
+				Type: "create_contract_v2",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"function":        "HostFunctionTypeHostFunctionTypeUploadContractWasm",
-				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
-				"type":            "upload_wasm",
+			result: InvokeHostFunctionDetail{
+				Function:      "HostFunctionTypeHostFunctionTypeUploadContractWasm",
+				LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				Type:          "upload_wasm",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"extend_to":       int32(1234),
-				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
-				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
-				"type":            "extend_footprint_ttl",
+			result: ExtendFootprintTtlDetail{
+				ExtendTo:      uint32(1234),
+				LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				ContractID:    "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
+				Type:          "extend_footprint_ttl",
 			},
 		},
 		{
 			err: nil,
-			result: map[string]interface{}{
-				"ledger_key_hash": []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
-				"contract_id":     "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
-				"type":            "restore_footprint",
+			result: RestoreFootprintDetail{
+				LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
+				ContractID:    "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W",
+				Type:          "restore_footprint",
 			},
 		},
 	}
diff --git a/ingest/liquidity_pool_withdraw_details.go b/ingest/liquidity_pool_withdraw_details.go
index 9c4de93b3b..a8e5249d57 100644
--- a/ingest/liquidity_pool_withdraw_details.go
+++ b/ingest/liquidity_pool_withdraw_details.go
@@ -81,7 +81,7 @@ func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (LiquidityPoolWithdrawD
 	liquidityPoolWithdrawDetail.ReserveBAssetCode = assetBCode
 	liquidityPoolWithdrawDetail.ReserveBAssetIssuer = assetBIssuer
 	liquidityPoolWithdrawDetail.ReserveBAssetType = assetBType
-	liquidityPoolWithdrawDetail.ReserveAWithdrawAmount = int64(receivedB)
+	liquidityPoolWithdrawDetail.ReserveBWithdrawAmount = int64(receivedB)
 
 	return liquidityPoolWithdrawDetail, nil
 }
diff --git a/ingest/payment_details.go b/ingest/payment_details.go
index c7845ed503..0266ec9f58 100644
--- a/ingest/payment_details.go
+++ b/ingest/payment_details.go
@@ -56,5 +56,9 @@ func (o *LedgerOperation) PaymentDetails() (PaymentDetail, error) {
 		return PaymentDetail{}, err
 	}
 
+	paymentDetail.AssetCode = assetCode
+	paymentDetail.AssetIssuer = assetIssuer
+	paymentDetail.AssetType = assetType
+
 	return paymentDetail, nil
 }

From 5c68c65ea0178d17f12438c2660a746ca8cdc220 Mon Sep 17 00:00:00 2001
From: chowbao <simon.chow765@gmail.com>
Date: Tue, 4 Feb 2025 11:18:42 -0500
Subject: [PATCH 09/12] Update hash.go

---
 xdr/hash.go | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/xdr/hash.go b/xdr/hash.go
index 084520d082..2a15c18c9c 100644
--- a/xdr/hash.go
+++ b/xdr/hash.go
@@ -1,8 +1,6 @@
 package xdr
 
-import (
-	"encoding/hex"
-)
+import "encoding/hex"
 
 func (h Hash) HexString() string {
 	return hex.EncodeToString(h[:])

From cc6d49df2de93fbffceb5557a714a3166d38de82 Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Thu, 6 Feb 2025 10:58:38 -0500
Subject: [PATCH 10/12] add string for js

---
 ingest/account_merge_details.go                 |  2 +-
 ingest/allow_trust_details.go                   |  2 +-
 ingest/bump_sequence_details.go                 |  2 +-
 ingest/change_trust_details.go                  |  4 ++--
 ingest/claim_claimable_balance_details.go       |  2 +-
 ingest/clawback_details.go                      |  4 ++--
 ingest/create_account_details.go                |  4 ++--
 ingest/create_claimable_balance_details.go      |  2 +-
 ingest/create_passive_sell_offer_details.go     |  2 +-
 ingest/end_sponsoring_future_reserve_details.go |  2 +-
 ingest/ledger_operation.go                      |  2 +-
 ingest/liquidity_pool_deposit_details.go        | 10 +++++-----
 ingest/liquidity_pool_withdraw_details.go       | 10 +++++-----
 ingest/manage_buy_offer_details.go              |  4 ++--
 ingest/manage_sell_offer_details.go             |  4 ++--
 ingest/path_payment_strict_receive_details.go   | 10 +++++-----
 ingest/path_payment_strict_send_details.go      | 10 +++++-----
 ingest/payment_details.go                       |  6 +++---
 18 files changed, 41 insertions(+), 41 deletions(-)

diff --git a/ingest/account_merge_details.go b/ingest/account_merge_details.go
index fc70291188..b439a49ac5 100644
--- a/ingest/account_merge_details.go
+++ b/ingest/account_merge_details.go
@@ -5,7 +5,7 @@ import "fmt"
 type AccountMergeDetail struct {
 	Account        string `json:"account"`
 	AccountMuxed   string `json:"account_muxed"`
-	AccountMuxedID uint64 `json:"account_muxed_id"`
+	AccountMuxedID uint64 `json:"account_muxed_id,string"`
 	Into           string `json:"into"`
 	IntoMuxed      string `json:"into_muxed"`
 	IntoMuxedID    uint64 `json:"into_muxed_id"`
diff --git a/ingest/allow_trust_details.go b/ingest/allow_trust_details.go
index f2cbed5cb8..3c3785c88b 100644
--- a/ingest/allow_trust_details.go
+++ b/ingest/allow_trust_details.go
@@ -13,7 +13,7 @@ type AllowTrustDetail struct {
 	Trustor                        string `json:"trustor"`
 	Trustee                        string `json:"trustee"`
 	TrusteeMuxed                   string `json:"trustee_muxed"`
-	TrusteeMuxedID                 uint64 `json:"trustee_muxed_id"`
+	TrusteeMuxedID                 uint64 `json:"trustee_muxed_id,string"`
 	Authorize                      bool   `json:"authorize"`
 	AuthorizeToMaintainLiabilities bool   `json:"authorize_to_maintain_liabilities"`
 	ClawbackEnabled                bool   `json:"clawback_enabled"`
diff --git a/ingest/bump_sequence_details.go b/ingest/bump_sequence_details.go
index b2e16a895c..abeb2a4e52 100644
--- a/ingest/bump_sequence_details.go
+++ b/ingest/bump_sequence_details.go
@@ -3,7 +3,7 @@ package ingest
 import "fmt"
 
 type BumpSequenceDetails struct {
-	BumpTo int64 `json:"bump_to"`
+	BumpTo int64 `json:"bump_to,string"`
 }
 
 func (o *LedgerOperation) BumpSequenceDetails() (BumpSequenceDetails, error) {
diff --git a/ingest/change_trust_details.go b/ingest/change_trust_details.go
index a3302146f7..ca3040bd2d 100644
--- a/ingest/change_trust_details.go
+++ b/ingest/change_trust_details.go
@@ -11,11 +11,11 @@ type ChangeTrustDetail struct {
 	AssetIssuer     string `json:"asset_issuer"`
 	AssetType       string `json:"asset_type"`
 	LiquidityPoolID string `json:"liquidity_pool_id"`
-	Limit           int64  `json:"limit"`
+	Limit           int64  `json:"limit,string"`
 	Trustee         string `json:"trustee"`
 	Trustor         string `json:"trustor"`
 	TrustorMuxed    string `json:"trustor_muxed"`
-	TrustorMuxedID  uint64 `json:"trustor_muxed_id"`
+	TrustorMuxedID  uint64 `json:"trustor_muxed_id,string"`
 }
 
 func (o *LedgerOperation) ChangeTrustDetails() (ChangeTrustDetail, error) {
diff --git a/ingest/claim_claimable_balance_details.go b/ingest/claim_claimable_balance_details.go
index 2125d31e6b..69d85950ad 100644
--- a/ingest/claim_claimable_balance_details.go
+++ b/ingest/claim_claimable_balance_details.go
@@ -10,7 +10,7 @@ type ClaimClaimableBalanceDetail struct {
 	BalanceID       string `json:"balance_id"`
 	Claimant        string `json:"claimant"`
 	ClaimantMuxed   string `json:"claimant_muxed"`
-	ClaimantMuxedID uint64 `json:"claimant_muxed_id"`
+	ClaimantMuxedID uint64 `json:"claimant_muxed_id,string"`
 }
 
 func (o *LedgerOperation) ClaimClaimableBalanceDetails() (ClaimClaimableBalanceDetail, error) {
diff --git a/ingest/clawback_details.go b/ingest/clawback_details.go
index f91dda8487..ff84f41705 100644
--- a/ingest/clawback_details.go
+++ b/ingest/clawback_details.go
@@ -8,8 +8,8 @@ type ClawbackDetail struct {
 	AssetType   string `json:"asset_type"`
 	From        string `json:"from"`
 	FromMuxed   string `json:"from_muxed"`
-	FromMuxedID uint64 `json:"from_muxed_id"`
-	Amount      int64  `json:"amount"`
+	FromMuxedID uint64 `json:"from_muxed_id,string"`
+	Amount      int64  `json:"amount,string"`
 }
 
 func (o *LedgerOperation) ClawbackDetails() (ClawbackDetail, error) {
diff --git a/ingest/create_account_details.go b/ingest/create_account_details.go
index 9a04de1be6..9eed63cc2d 100644
--- a/ingest/create_account_details.go
+++ b/ingest/create_account_details.go
@@ -6,10 +6,10 @@ import (
 
 type CreateAccountDetail struct {
 	Account         string `json:"account"`
-	StartingBalance int64  `json:"starting_balance"`
+	StartingBalance int64  `json:"starting_balance,string"`
 	Funder          string `json:"funder"`
 	FunderMuxed     string `json:"funder_muxed"`
-	FunderMuxedID   uint64 `json:"funder_muxed_id"`
+	FunderMuxedID   uint64 `json:"funder_muxed_id,string"`
 }
 
 func (o *LedgerOperation) CreateAccountDetails() (CreateAccountDetail, error) {
diff --git a/ingest/create_claimable_balance_details.go b/ingest/create_claimable_balance_details.go
index 795ed5edf0..0aefcf1ffb 100644
--- a/ingest/create_claimable_balance_details.go
+++ b/ingest/create_claimable_balance_details.go
@@ -8,7 +8,7 @@ type CreateClaimableBalanceDetail struct {
 	AssetCode   string     `json:"asset_code"`
 	AssetIssuer string     `json:"asset_issuer"`
 	AssetType   string     `json:"asset_type"`
-	Amount      int64      `json:"amount"`
+	Amount      int64      `json:"amount,string"`
 	Claimants   []Claimant `json:"claimants"`
 }
 
diff --git a/ingest/create_passive_sell_offer_details.go b/ingest/create_passive_sell_offer_details.go
index 1532cb7d07..2ee8791b8c 100644
--- a/ingest/create_passive_sell_offer_details.go
+++ b/ingest/create_passive_sell_offer_details.go
@@ -6,7 +6,7 @@ import (
 )
 
 type CreatePassiveSellOfferDetail struct {
-	Amount             int64   `json:"amount"`
+	Amount             int64   `json:"amount,string"`
 	PriceN             int32   `json:"price_n"`
 	PriceD             int32   `json:"price_d"`
 	Price              float64 `json:"price"`
diff --git a/ingest/end_sponsoring_future_reserve_details.go b/ingest/end_sponsoring_future_reserve_details.go
index 5a9b58df9e..c2d6f609e3 100644
--- a/ingest/end_sponsoring_future_reserve_details.go
+++ b/ingest/end_sponsoring_future_reserve_details.go
@@ -3,7 +3,7 @@ package ingest
 type EndSponsoringFutureReserveDetail struct {
 	BeginSponsor        string `json:"begin_sponsor"`
 	BeginSponsorMuxed   string `json:"begin_sponsor_muxed"`
-	BeginSponsorMuxedID uint64 `json:"begin_sponsor_muxed_id"`
+	BeginSponsorMuxedID uint64 `json:"begin_sponsor_muxed_id,string"`
 }
 
 func (o *LedgerOperation) EndSponsoringFutureReserveDetails() (EndSponsoringFutureReserveDetail, error) {
diff --git a/ingest/ledger_operation.go b/ingest/ledger_operation.go
index 3b6d4e49f1..62f39c8271 100644
--- a/ingest/ledger_operation.go
+++ b/ingest/ledger_operation.go
@@ -257,7 +257,7 @@ type LedgerKeyDetail struct {
 	ClaimableBalanceID       string `json:"claimable_balance_id"`
 	DataAccountID            string `json:"data_account_id"`
 	DataName                 string `json:"data_name"`
-	OfferID                  int64  `json:"offer_id"`
+	OfferID                  int64  `json:"offer_id,string"`
 	TrustlineAccountID       string `json:"trustline_account_id"`
 	TrustlineLiquidityPoolID string `json:"trustline_liquidity_pool_id"`
 	TrustlineAssetCode       string `json:"trustline_asset_code"`
diff --git a/ingest/liquidity_pool_deposit_details.go b/ingest/liquidity_pool_deposit_details.go
index 39e65288c5..8d7dce32d6 100644
--- a/ingest/liquidity_pool_deposit_details.go
+++ b/ingest/liquidity_pool_deposit_details.go
@@ -12,20 +12,20 @@ type LiquidityPoolDepositDetail struct {
 	ReserveAAssetCode     string  `json:"reserve_a_asset_code"`
 	ReserveAAssetIssuer   string  `json:"reserve_a_asset_issuer"`
 	ReserveAAssetType     string  `json:"reserve_a_asset_type"`
-	ReserveAMaxAmount     int64   `json:"reserve_a_max_amount"`
-	ReserveADepositAmount int64   `json:"reserve_a_deposit_amount"`
+	ReserveAMaxAmount     int64   `json:"reserve_a_max_amount,string"`
+	ReserveADepositAmount int64   `json:"reserve_a_deposit_amount,string"`
 	ReserveBAssetCode     string  `json:"reserve_b_asset_code"`
 	ReserveBAssetIssuer   string  `json:"reserve_b_asset_issuer"`
 	ReserveBAssetType     string  `json:"reserve_b_asset_type"`
-	ReserveBMaxAmount     int64   `json:"reserve_b_max_amount"`
-	ReserveBDepositAmount int64   `json:"reserve_b_deposit_amount"`
+	ReserveBMaxAmount     int64   `json:"reserve_b_max_amount,string"`
+	ReserveBDepositAmount int64   `json:"reserve_b_deposit_amount,string"`
 	MinPriceN             int32   `json:"min_price_n"`
 	MinPriceD             int32   `json:"min_price_d"`
 	MinPrice              float64 `json:"min_price"`
 	MaxPriceN             int32   `json:"max_price_n"`
 	MaxPriceD             int32   `json:"max_price_d"`
 	MaxPrice              float64 `json:"max_price"`
-	SharesReceived        int64   `json:"shares_received"`
+	SharesReceived        int64   `json:"shares_received,string"`
 }
 
 func (o *LedgerOperation) LiquidityPoolDepositDetails() (LiquidityPoolDepositDetail, error) {
diff --git a/ingest/liquidity_pool_withdraw_details.go b/ingest/liquidity_pool_withdraw_details.go
index a8e5249d57..94d4a93ac7 100644
--- a/ingest/liquidity_pool_withdraw_details.go
+++ b/ingest/liquidity_pool_withdraw_details.go
@@ -11,14 +11,14 @@ type LiquidityPoolWithdrawDetail struct {
 	ReserveAAssetCode      string `json:"reserve_a_asset_code"`
 	ReserveAAssetIssuer    string `json:"reserve_a_asset_issuer"`
 	ReserveAAssetType      string `json:"reserve_a_asset_type"`
-	ReserveAMinAmount      int64  `json:"reserve_a_min_amount"`
-	ReserveAWithdrawAmount int64  `json:"reserve_a_withdraw_amount"`
+	ReserveAMinAmount      int64  `json:"reserve_a_min_amount,string"`
+	ReserveAWithdrawAmount int64  `json:"reserve_a_withdraw_amount,string"`
 	ReserveBAssetCode      string `json:"reserve_b_asset_code"`
 	ReserveBAssetIssuer    string `json:"reserve_b_asset_issuer"`
 	ReserveBAssetType      string `json:"reserve_b_asset_type"`
-	ReserveBMinAmount      int64  `json:"reserve_b_min_amount"`
-	ReserveBWithdrawAmount int64  `json:"reserve_b_withdraw_amount"`
-	Shares                 int64  `json:"shares"`
+	ReserveBMinAmount      int64  `json:"reserve_b_min_amount,string"`
+	ReserveBWithdrawAmount int64  `json:"reserve_b_withdraw_amount,string"`
+	Shares                 int64  `json:"shares,string"`
 }
 
 func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (LiquidityPoolWithdrawDetail, error) {
diff --git a/ingest/manage_buy_offer_details.go b/ingest/manage_buy_offer_details.go
index 21c9ac35ab..81e39982a8 100644
--- a/ingest/manage_buy_offer_details.go
+++ b/ingest/manage_buy_offer_details.go
@@ -6,8 +6,8 @@ import (
 )
 
 type ManageBuyOffer struct {
-	OfferID            int64   `json:"offer_id"`
-	Amount             int64   `json:"amount"`
+	OfferID            int64   `json:"offer_id,string"`
+	Amount             int64   `json:"amount,string"`
 	PriceN             int32   `json:"price_n"`
 	PriceD             int32   `json:"price_d"`
 	Price              float64 `json:"price"`
diff --git a/ingest/manage_sell_offer_details.go b/ingest/manage_sell_offer_details.go
index 9b33fd4b03..5fc7811ce1 100644
--- a/ingest/manage_sell_offer_details.go
+++ b/ingest/manage_sell_offer_details.go
@@ -6,8 +6,8 @@ import (
 )
 
 type ManageSellOffer struct {
-	OfferID            int64   `json:"offer_id"`
-	Amount             int64   `json:"amount"`
+	OfferID            int64   `json:"offer_id,string"`
+	Amount             int64   `json:"amount,string"`
 	PriceN             int32   `json:"price_n"`
 	PriceD             int32   `json:"price_d"`
 	Price              float64 `json:"price"`
diff --git a/ingest/path_payment_strict_receive_details.go b/ingest/path_payment_strict_receive_details.go
index 3a9eab83d4..31dd64d514 100644
--- a/ingest/path_payment_strict_receive_details.go
+++ b/ingest/path_payment_strict_receive_details.go
@@ -7,19 +7,19 @@ import (
 type PathPaymentStrictReceiveDetail struct {
 	From              string `json:"from"`
 	FromMuxed         string `json:"from_muxed"`
-	FromMuxedID       uint64 `json:"from_muxed_id"`
+	FromMuxedID       uint64 `json:"from_muxed_id,string"`
 	To                string `json:"to"`
 	ToMuxed           string `json:"to_muxed"`
-	ToMuxedID         uint64 `json:"to_muxed_id"`
+	ToMuxedID         uint64 `json:"to_muxed_id,string"`
 	AssetCode         string `json:"asset_code"`
 	AssetIssuer       string `json:"asset_issuer"`
 	AssetType         string `json:"asset_type"`
-	Amount            int64  `json:"amount"`
+	Amount            int64  `json:"amount,string"`
 	SourceAssetCode   string `json:"source_asset_code"`
 	SourceAssetIssuer string `json:"source_asset_issuer"`
 	SourceAssetType   string `json:"source_asset_type"`
-	SourceAmount      int64  `json:"source_amount"`
-	SourceMax         int64  `json:"source_max"`
+	SourceAmount      int64  `json:"source_amount,string"`
+	SourceMax         int64  `json:"source_max,string"`
 	Path              []Path `json:"path"`
 }
 
diff --git a/ingest/path_payment_strict_send_details.go b/ingest/path_payment_strict_send_details.go
index 6b0c28e69f..1a28b8d173 100644
--- a/ingest/path_payment_strict_send_details.go
+++ b/ingest/path_payment_strict_send_details.go
@@ -7,19 +7,19 @@ import (
 type PathPaymentStrictSendDetail struct {
 	From              string `json:"from"`
 	FromMuxed         string `json:"from_muxed"`
-	FromMuxedID       uint64 `json:"from_muxed_id"`
+	FromMuxedID       uint64 `json:"from_muxed_id,string"`
 	To                string `json:"to"`
 	ToMuxed           string `json:"to_muxed"`
-	ToMuxedID         uint64 `json:"to_muxed_id"`
+	ToMuxedID         uint64 `json:"to_muxed_id,string"`
 	AssetCode         string `json:"asset_code"`
 	AssetIssuer       string `json:"asset_issuer"`
 	AssetType         string `json:"asset_type"`
-	Amount            int64  `json:"amount"`
+	Amount            int64  `json:"amount,string"`
 	SourceAssetCode   string `json:"source_asset_code"`
 	SourceAssetIssuer string `json:"source_asset_issuer"`
 	SourceAssetType   string `json:"source_asset_type"`
-	SourceAmount      int64  `json:"source_amount"`
-	DestinationMin    int64  `json:"destination_min"`
+	SourceAmount      int64  `json:"source_amount,string"`
+	DestinationMin    int64  `json:"destination_min,string"`
 	Path              []Path `json:"path"`
 }
 
diff --git a/ingest/payment_details.go b/ingest/payment_details.go
index 0266ec9f58..c209db38ee 100644
--- a/ingest/payment_details.go
+++ b/ingest/payment_details.go
@@ -7,14 +7,14 @@ import (
 type PaymentDetail struct {
 	From        string `json:"from"`
 	FromMuxed   string `json:"from_muxed"`
-	FromMuxedID uint64 `json:"from_muxed_id"`
+	FromMuxedID uint64 `json:"from_muxed_id,string"`
 	To          string `json:"to"`
 	ToMuxed     string `json:"to_muxed"`
-	ToMuxedID   uint64 `json:"to_muxed_id"`
+	ToMuxedID   uint64 `json:"to_muxed_id,string"`
 	AssetCode   string `json:"asset_code"`
 	AssetIssuer string `json:"asset_issuer"`
 	AssetType   string `json:"asset_type"`
-	Amount      int64  `json:"amount"`
+	Amount      int64  `json:"amount,string"`
 }
 
 func (o *LedgerOperation) PaymentDetails() (PaymentDetail, error) {

From 0258c833933253b1b6e6f5a0c9f6bf9ac8a0567f Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Thu, 6 Feb 2025 11:07:08 -0500
Subject: [PATCH 11/12] make createcontract better

---
 ingest/invoke_host_function_details.go | 67 +++++++++++---------------
 1 file changed, 28 insertions(+), 39 deletions(-)

diff --git a/ingest/invoke_host_function_details.go b/ingest/invoke_host_function_details.go
index 8a37365602..58a2720c46 100644
--- a/ingest/invoke_host_function_details.go
+++ b/ingest/invoke_host_function_details.go
@@ -70,30 +70,13 @@ func (o *LedgerOperation) InvokeHostFunctionDetails() (InvokeHostFunctionDetail,
 		args := op.HostFunction.MustCreateContract()
 
 		invokeHostFunctionDetail.Type = "create_contract"
-		invokeHostFunctionDetail.LedgerKeyHash = o.Transaction.LedgerKeyHashFromTxEnvelope()
-
-		var contractID string
-		contractID, ok = o.Transaction.contractIdFromTxEnvelope()
-		if ok {
-			invokeHostFunctionDetail.ContractID = contractID
-		}
-
-		var contractCodeHash string
-		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
-		if ok {
-			invokeHostFunctionDetail.ContractCodeHash = contractCodeHash
-		}
 
 		preImageDetails, err := switchContractIdPreimageType(args.ContractIdPreimage)
 		if err != nil {
 			return InvokeHostFunctionDetail{}, nil
 		}
 
-		invokeHostFunctionDetail.From = preImageDetails.From
-		invokeHostFunctionDetail.Address = preImageDetails.Address
-		invokeHostFunctionDetail.AssetCode = preImageDetails.AssetCode
-		invokeHostFunctionDetail.AssetIssuer = preImageDetails.AssetIssuer
-		invokeHostFunctionDetail.AssetType = preImageDetails.AssetType
+		o.getCreateContractDetails(&invokeHostFunctionDetail, preImageDetails)
 	case xdr.HostFunctionTypeHostFunctionTypeUploadContractWasm:
 		invokeHostFunctionDetail.Type = "upload_wasm"
 		invokeHostFunctionDetail.LedgerKeyHash = o.Transaction.LedgerKeyHashFromTxEnvelope()
@@ -107,38 +90,44 @@ func (o *LedgerOperation) InvokeHostFunctionDetails() (InvokeHostFunctionDetail,
 		args := op.HostFunction.MustCreateContractV2()
 
 		invokeHostFunctionDetail.Type = "create_contract_v2"
-		invokeHostFunctionDetail.LedgerKeyHash = o.Transaction.LedgerKeyHashFromTxEnvelope()
 
-		var contractID string
-		contractID, ok = o.Transaction.contractIdFromTxEnvelope()
-		if ok {
-			invokeHostFunctionDetail.ContractID = contractID
+		preImageDetails, err := switchContractIdPreimageType(args.ContractIdPreimage)
+		if err != nil {
+			return InvokeHostFunctionDetail{}, err
 		}
 
-		var contractCodeHash string
-		contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
-		if ok {
-			invokeHostFunctionDetail.ContractCodeHash = contractCodeHash
-		}
+		o.getCreateContractDetails(&invokeHostFunctionDetail, preImageDetails)
 
 		// ConstructorArgs is a list of ScVals
 		// This will initially be handled the same as InvokeContractParams until a different
 		// model is found necessary.
 		invokeHostFunctionDetail.Parameters, invokeHostFunctionDetail.ParametersDecoded = o.serializeParameters(args.ConstructorArgs)
-
-		preImageDetails, err := switchContractIdPreimageType(args.ContractIdPreimage)
-		if err != nil {
-			return InvokeHostFunctionDetail{}, nil
-		}
-
-		invokeHostFunctionDetail.From = preImageDetails.From
-		invokeHostFunctionDetail.Address = preImageDetails.Address
-		invokeHostFunctionDetail.AssetCode = preImageDetails.AssetCode
-		invokeHostFunctionDetail.AssetIssuer = preImageDetails.AssetIssuer
-		invokeHostFunctionDetail.AssetType = preImageDetails.AssetType
 	default:
 		return InvokeHostFunctionDetail{}, fmt.Errorf("unknown host function type: %s", op.HostFunction.Type)
 	}
 
 	return invokeHostFunctionDetail, nil
 }
+
+func (o *LedgerOperation) getCreateContractDetails(invokeHostFunctionDetail *InvokeHostFunctionDetail, preImageDetails PreImageDetails) {
+	var ok bool
+	invokeHostFunctionDetail.LedgerKeyHash = o.Transaction.LedgerKeyHashFromTxEnvelope()
+
+	var contractID string
+	contractID, ok = o.Transaction.contractIdFromTxEnvelope()
+	if ok {
+		invokeHostFunctionDetail.ContractID = contractID
+	}
+
+	var contractCodeHash string
+	contractCodeHash, ok = o.Transaction.ContractCodeHashFromTxEnvelope()
+	if ok {
+		invokeHostFunctionDetail.ContractCodeHash = contractCodeHash
+	}
+
+	invokeHostFunctionDetail.From = preImageDetails.From
+	invokeHostFunctionDetail.Address = preImageDetails.Address
+	invokeHostFunctionDetail.AssetCode = preImageDetails.AssetCode
+	invokeHostFunctionDetail.AssetIssuer = preImageDetails.AssetIssuer
+	invokeHostFunctionDetail.AssetType = preImageDetails.AssetType
+}

From 52353e1b974c222560f56fc53cc60760d5187b57 Mon Sep 17 00:00:00 2001
From: Simon Chow <simon.chow@stellar.org>
Date: Thu, 13 Feb 2025 19:53:23 -0500
Subject: [PATCH 12/12] address comments

---
 go.mod                                    |   1 +
 go.sum                                    |   2 +
 ingest/account_merge_details.go           |   2 +-
 ingest/invoke_host_function_details.go    |  14 ++-
 ingest/ledger_operation.go                |  40 ++++----
 ingest/ledger_operation_test.go           | 112 ++++++++++------------
 ingest/liquidity_pool_deposit_details.go  |  70 +++++++-------
 ingest/liquidity_pool_withdraw_details.go |  42 ++++----
 8 files changed, 142 insertions(+), 141 deletions(-)

diff --git a/go.mod b/go.mod
index 6d7e5fab5d..1a5c1e73ed 100644
--- a/go.mod
+++ b/go.mod
@@ -62,6 +62,7 @@ require (
 	github.com/docker/docker v27.3.1+incompatible
 	github.com/docker/go-connections v0.5.0
 	github.com/fsouza/fake-gcs-server v1.49.2
+	github.com/stellar/go-stellar-xdr-json v0.0.0-20250203230501-c6fa0788fe99
 )
 
 require (
diff --git a/go.sum b/go.sum
index b071601459..8ecbbccca2 100644
--- a/go.sum
+++ b/go.sum
@@ -469,6 +469,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
 github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
+github.com/stellar/go-stellar-xdr-json v0.0.0-20250203230501-c6fa0788fe99 h1:m6P+35IPHoxYjAnEgZOgSiDXwEnFCIOpXdwNt8tZ/cw=
+github.com/stellar/go-stellar-xdr-json v0.0.0-20250203230501-c6fa0788fe99/go.mod h1:BQ9LWqVJ14qa7ctsyLmjEHSx3Pi7r/8A0mbKzib4cgs=
 github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 h1:OzCVd0SV5qE3ZcDeSFCmOWLZfEWZ3Oe8KtmSOYKEVWE=
 github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps=
 github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible h1:jMXXAcz6xTarGDQ4VtVbtERogcmDQw4RaE85Cr9CgoQ=
diff --git a/ingest/account_merge_details.go b/ingest/account_merge_details.go
index b439a49ac5..f75ed00d1a 100644
--- a/ingest/account_merge_details.go
+++ b/ingest/account_merge_details.go
@@ -8,7 +8,7 @@ type AccountMergeDetail struct {
 	AccountMuxedID uint64 `json:"account_muxed_id,string"`
 	Into           string `json:"into"`
 	IntoMuxed      string `json:"into_muxed"`
-	IntoMuxedID    uint64 `json:"into_muxed_id"`
+	IntoMuxedID    uint64 `json:"into_muxed_id,string"`
 }
 
 func (o *LedgerOperation) AccountMergeDetails() (AccountMergeDetail, error) {
diff --git a/ingest/invoke_host_function_details.go b/ingest/invoke_host_function_details.go
index 58a2720c46..69a74f8d16 100644
--- a/ingest/invoke_host_function_details.go
+++ b/ingest/invoke_host_function_details.go
@@ -12,8 +12,7 @@ type InvokeHostFunctionDetail struct {
 	LedgerKeyHash       []string              `json:"ledger_key_hash"`
 	ContractID          string                `json:"contract_id"`
 	ContractCodeHash    string                `json:"contract_code_hash"`
-	Parameters          []map[string]string   `json:"parameters"`
-	ParametersDecoded   []map[string]string   `json:"parameters_decoded"`
+	Parameters          []interface{}         `json:"parameters"`
 	AssetBalanceChanges []BalanceChangeDetail `json:"asset_balance_changes"`
 	From                string                `json:"from"`
 	Address             string                `json:"address"`
@@ -56,8 +55,10 @@ func (o *LedgerOperation) InvokeHostFunctionDetails() (InvokeHostFunctionDetail,
 			invokeHostFunctionDetail.ContractCodeHash = contractCodeHash
 		}
 
-		// TODO: Parameters should be processed with xdr2json
-		invokeHostFunctionDetail.Parameters, invokeHostFunctionDetail.ParametersDecoded = o.serializeParameters(args)
+		invokeHostFunctionDetail.Parameters, err = o.serializeParameters(args)
+		if err != nil {
+			return InvokeHostFunctionDetail{}, err
+		}
 
 		balanceChanges, err := o.parseAssetBalanceChangesFromContractEvents()
 		if err != nil {
@@ -101,7 +102,10 @@ func (o *LedgerOperation) InvokeHostFunctionDetails() (InvokeHostFunctionDetail,
 		// ConstructorArgs is a list of ScVals
 		// This will initially be handled the same as InvokeContractParams until a different
 		// model is found necessary.
-		invokeHostFunctionDetail.Parameters, invokeHostFunctionDetail.ParametersDecoded = o.serializeParameters(args.ConstructorArgs)
+		invokeHostFunctionDetail.Parameters, err = o.serializeParameters(args.ConstructorArgs)
+		if err != nil {
+			return InvokeHostFunctionDetail{}, err
+		}
 	default:
 		return InvokeHostFunctionDetail{}, fmt.Errorf("unknown host function type: %s", op.HostFunction.Type)
 	}
diff --git a/ingest/ledger_operation.go b/ingest/ledger_operation.go
index 62f39c8271..981ac8bfb2 100644
--- a/ingest/ledger_operation.go
+++ b/ingest/ledger_operation.go
@@ -1,11 +1,12 @@
 package ingest
 
 import (
-	"encoding/base64"
+	"encoding/json"
 	"fmt"
 	"math/big"
 
 	"github.com/dgryski/go-farm"
+	"github.com/stellar/go-stellar-xdr-json/xdr2json"
 	"github.com/stellar/go/amount"
 	"github.com/stellar/go/support/contractevents"
 	"github.com/stellar/go/toid"
@@ -362,32 +363,29 @@ func (o *LedgerOperation) getLiquidityPoolAndProductDelta(lpID *xdr.PoolId) (*xd
 	return nil, nil, fmt.Errorf("liquidity pool change not found")
 }
 
-func (o *LedgerOperation) serializeParameters(args []xdr.ScVal) ([]map[string]string, []map[string]string) {
-	params := make([]map[string]string, 0, len(args))
-	paramsDecoded := make([]map[string]string, 0, len(args))
+func (o *LedgerOperation) serializeParameters(args []xdr.ScVal) ([]interface{}, error) {
+	var params []interface{}
 
 	for _, param := range args {
-		serializedParam := map[string]string{}
-		serializedParam["value"] = "n/a"
-		serializedParam["type"] = "n/a"
-
-		serializedParamDecoded := map[string]string{}
-		serializedParamDecoded["value"] = "n/a"
-		serializedParamDecoded["type"] = "n/a"
-
-		if scValTypeName, ok := param.ArmForSwitch(int32(param.Type)); ok {
-			serializedParam["type"] = scValTypeName
-			serializedParamDecoded["type"] = scValTypeName
-			if raw, err := param.MarshalBinary(); err == nil {
-				serializedParam["value"] = base64.StdEncoding.EncodeToString(raw)
-				serializedParamDecoded["value"] = param.String()
+		if _, ok := param.ArmForSwitch(int32(param.Type)); ok {
+			var err error
+			var raw []byte
+			var jsonMessage json.RawMessage
+			raw, err = param.MarshalBinary()
+			if err != nil {
+				return nil, err
 			}
+
+			jsonMessage, err = xdr2json.ConvertBytes(xdr.ScVal{}, raw)
+			if err != nil {
+				return nil, err
+			}
+
+			params = append(params, jsonMessage)
 		}
-		params = append(params, serializedParam)
-		paramsDecoded = append(paramsDecoded, serializedParamDecoded)
 	}
 
-	return params, paramsDecoded
+	return params, nil
 }
 
 func (o *LedgerOperation) parseAssetBalanceChangesFromContractEvents() ([]BalanceChangeDetail, error) {
diff --git a/ingest/ledger_operation_test.go b/ingest/ledger_operation_test.go
index f70a8534c7..74c6cb6d37 100644
--- a/ingest/ledger_operation_test.go
+++ b/ingest/ledger_operation_test.go
@@ -1,12 +1,16 @@
 package ingest
 
 import (
+	"encoding/json"
 	"testing"
 
 	"github.com/stellar/go/xdr"
 	"github.com/stretchr/testify/assert"
 )
 
+var testScSymbol xdr.ScSymbol = "test"
+var testScBool bool = true
+
 func TestOperation(t *testing.T) {
 	o := LedgerOperation{
 		OperationIndex:    int32(0),
@@ -1392,41 +1396,50 @@ func resultTestOutput() []testOutput {
 		{
 			err: nil,
 			result: LiquidityPoolDepositDetail{
-				LiquidityPoolID:       "AQIDBAUGBwgJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
-				MaxPrice:              1e+06,
-				MaxPriceN:             1000000,
-				MaxPriceD:             1,
-				MinPrice:              1e-06,
-				MinPriceN:             1,
-				MinPriceD:             1000000,
-				ReserveAAssetCode:     "USDT",
-				ReserveAAssetIssuer:   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				ReserveAAssetType:     "credit_alphanum4",
-				ReserveADepositAmount: int64(1),
-				ReserveAMaxAmount:     int64(1000),
-				ReserveBAssetCode:     "USDT",
-				ReserveBAssetIssuer:   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				ReserveBAssetType:     "credit_alphanum4",
-				ReserveBDepositAmount: 1,
-				ReserveBMaxAmount:     int64(100),
-				SharesReceived:        1,
+				LiquidityPoolID: "AQIDBAUGBwgJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
+				MaxPrice:        1e+06,
+				MaxPriceN:       1000000,
+				MaxPriceD:       1,
+				MinPrice:        1e-06,
+				MinPriceN:       1,
+				MinPriceD:       1000000,
+				ReserveAssetA: ReserveAsset{
+					AssetCode:     "USDT",
+					AssetIssuer:   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+					AssetType:     "credit_alphanum4",
+					DepositAmount: int64(1),
+					MaxAmount:     int64(1000),
+				},
+				ReserveAssetB: ReserveAsset{
+					AssetCode:     "USDT",
+					AssetIssuer:   "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+					AssetType:     "credit_alphanum4",
+					DepositAmount: 1,
+					MaxAmount:     int64(100),
+				},
+				SharesReceived: 1,
 			},
 		},
 		{
 			err: nil,
 			result: LiquidityPoolWithdrawDetail{
-				LiquidityPoolID:        "AQIDBAUGBwgJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
-				ReserveAAssetCode:      "USDT",
-				ReserveAAssetIssuer:    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				ReserveAAssetType:      "credit_alphanum4",
-				ReserveAMinAmount:      int64(1),
-				ReserveAWithdrawAmount: int64(-1),
-				ReserveBAssetCode:      "USDT",
-				ReserveBAssetIssuer:    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
-				ReserveBAssetType:      "credit_alphanum4",
-				ReserveBMinAmount:      int64(1),
-				ReserveBWithdrawAmount: int64(-1),
-				Shares:                 int64(4),
+				LiquidityPoolID: "AQIDBAUGBwgJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
+				ReserveAssetA: ReserveAsset{
+					AssetCode:      "USDT",
+					AssetIssuer:    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+					AssetType:      "credit_alphanum4",
+					MinAmount:      int64(1),
+					WithdrawAmount: int64(-1),
+				},
+
+				ReserveAssetB: ReserveAsset{
+					AssetCode:      "USDT",
+					AssetIssuer:    "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA",
+					AssetType:      "credit_alphanum4",
+					MinAmount:      int64(1),
+					WithdrawAmount: int64(-1),
+				},
+				Shares: int64(4),
 			},
 		},
 		{
@@ -1436,25 +1449,15 @@ func resultTestOutput() []testOutput {
 				ContractID:          "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37",
 				Function:            "HostFunctionTypeHostFunctionTypeInvokeContract",
 				LedgerKeyHash:       []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
-				Parameters: []map[string]string{
-					{
-						"type":  "Address",
-						"value": "AAAAEgAAAAESNFZ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
-					},
-					{
-						"type":  "Sym",
-						"value": "AAAADwAAAAR0ZXN0",
-					},
-				},
-				ParametersDecoded: []map[string]string{
-					{
-						"type":  "Address",
-						"value": "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37",
-					},
-					{
-						"type":  "Sym",
-						"value": "test",
+				Parameters: []interface{}{
+					json.RawMessage{
+						0x7b, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x22, 0x43, 0x41, 0x4a, 0x44,
+						0x49, 0x56, 0x54, 0x59, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+						0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+						0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+						0x42, 0x52, 0x33, 0x37, 0x22, 0x7d,
 					},
+					json.RawMessage{0x7b, 0x22, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x22, 0x3a, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x7d},
 				},
 				Type: "invoke_contract",
 			},
@@ -1493,17 +1496,8 @@ func resultTestOutput() []testOutput {
 				From:          "asset",
 				Function:      "HostFunctionTypeHostFunctionTypeCreateContractV2",
 				LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="},
-				Parameters: []map[string]string{
-					{
-						"type":  "B",
-						"value": "AAAAAAAAAAE=",
-					},
-				},
-				ParametersDecoded: []map[string]string{
-					{
-						"type":  "B",
-						"value": "true",
-					},
+				Parameters: []interface{}{
+					json.RawMessage{0x7b, 0x22, 0x62, 0x6f, 0x6f, 0x6c, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x7d},
 				},
 				Type: "create_contract_v2",
 			},
diff --git a/ingest/liquidity_pool_deposit_details.go b/ingest/liquidity_pool_deposit_details.go
index 8d7dce32d6..d4d38635f8 100644
--- a/ingest/liquidity_pool_deposit_details.go
+++ b/ingest/liquidity_pool_deposit_details.go
@@ -8,24 +8,26 @@ import (
 )
 
 type LiquidityPoolDepositDetail struct {
-	LiquidityPoolID       string  `json:"liquidity_pool_id"`
-	ReserveAAssetCode     string  `json:"reserve_a_asset_code"`
-	ReserveAAssetIssuer   string  `json:"reserve_a_asset_issuer"`
-	ReserveAAssetType     string  `json:"reserve_a_asset_type"`
-	ReserveAMaxAmount     int64   `json:"reserve_a_max_amount,string"`
-	ReserveADepositAmount int64   `json:"reserve_a_deposit_amount,string"`
-	ReserveBAssetCode     string  `json:"reserve_b_asset_code"`
-	ReserveBAssetIssuer   string  `json:"reserve_b_asset_issuer"`
-	ReserveBAssetType     string  `json:"reserve_b_asset_type"`
-	ReserveBMaxAmount     int64   `json:"reserve_b_max_amount,string"`
-	ReserveBDepositAmount int64   `json:"reserve_b_deposit_amount,string"`
-	MinPriceN             int32   `json:"min_price_n"`
-	MinPriceD             int32   `json:"min_price_d"`
-	MinPrice              float64 `json:"min_price"`
-	MaxPriceN             int32   `json:"max_price_n"`
-	MaxPriceD             int32   `json:"max_price_d"`
-	MaxPrice              float64 `json:"max_price"`
-	SharesReceived        int64   `json:"shares_received,string"`
+	LiquidityPoolID string       `json:"liquidity_pool_id"`
+	ReserveAssetA   ReserveAsset `json:"reserve_asset_a"`
+	ReserveAssetB   ReserveAsset `json:"reserve_asset_b"`
+	MinPriceN       int32        `json:"min_price_n"`
+	MinPriceD       int32        `json:"min_price_d"`
+	MinPrice        float64      `json:"min_price"`
+	MaxPriceN       int32        `json:"max_price_n"`
+	MaxPriceD       int32        `json:"max_price_d"`
+	MaxPrice        float64      `json:"max_price"`
+	SharesReceived  int64        `json:"shares_received,string"`
+}
+
+type ReserveAsset struct {
+	AssetCode      string `json:"asset_code"`
+	AssetIssuer    string `json:"asset_issuer"`
+	AssetType      string `json:"asset_type"`
+	MinAmount      int64  `json:"min_amount,string"`
+	MaxAmount      int64  `json:"max_amount,string"`
+	DepositAmount  int64  `json:"deposit_amount,string"`
+	WithdrawAmount int64  `json:"withdraw_amount,string"`
 }
 
 func (o *LedgerOperation) LiquidityPoolDepositDetails() (LiquidityPoolDepositDetail, error) {
@@ -35,12 +37,16 @@ func (o *LedgerOperation) LiquidityPoolDepositDetails() (LiquidityPoolDepositDet
 	}
 
 	liquidityPoolDepositDetail := LiquidityPoolDepositDetail{
-		ReserveAMaxAmount: int64(op.MaxAmountA),
-		ReserveBMaxAmount: int64(op.MaxAmountB),
-		MinPriceN:         int32(op.MinPrice.N),
-		MinPriceD:         int32(op.MinPrice.D),
-		MaxPriceN:         int32(op.MaxPrice.N),
-		MaxPriceD:         int32(op.MaxPrice.D),
+		ReserveAssetA: ReserveAsset{
+			MaxAmount: int64(op.MaxAmountA),
+		},
+		ReserveAssetB: ReserveAsset{
+			MaxAmount: int64(op.MaxAmountB),
+		},
+		MinPriceN: int32(op.MinPrice.N),
+		MinPriceD: int32(op.MinPrice.D),
+		MaxPriceN: int32(op.MaxPrice.N),
+		MaxPriceD: int32(op.MaxPrice.D),
 	}
 
 	var err error
@@ -79,10 +85,10 @@ func (o *LedgerOperation) LiquidityPoolDepositDetails() (LiquidityPoolDepositDet
 		return LiquidityPoolDepositDetail{}, err
 	}
 
-	liquidityPoolDepositDetail.ReserveAAssetCode = assetACode
-	liquidityPoolDepositDetail.ReserveAAssetIssuer = assetAIssuer
-	liquidityPoolDepositDetail.ReserveAAssetType = assetAType
-	liquidityPoolDepositDetail.ReserveADepositAmount = int64(depositedA)
+	liquidityPoolDepositDetail.ReserveAssetA.AssetCode = assetACode
+	liquidityPoolDepositDetail.ReserveAssetA.AssetIssuer = assetAIssuer
+	liquidityPoolDepositDetail.ReserveAssetA.AssetType = assetAType
+	liquidityPoolDepositDetail.ReserveAssetA.DepositAmount = int64(depositedA)
 
 	//Process ReserveB Details
 	var assetBCode, assetBIssuer, assetBType string
@@ -91,10 +97,10 @@ func (o *LedgerOperation) LiquidityPoolDepositDetails() (LiquidityPoolDepositDet
 		return LiquidityPoolDepositDetail{}, err
 	}
 
-	liquidityPoolDepositDetail.ReserveBAssetCode = assetBCode
-	liquidityPoolDepositDetail.ReserveBAssetIssuer = assetBIssuer
-	liquidityPoolDepositDetail.ReserveBAssetType = assetBType
-	liquidityPoolDepositDetail.ReserveBDepositAmount = int64(depositedB)
+	liquidityPoolDepositDetail.ReserveAssetB.AssetCode = assetBCode
+	liquidityPoolDepositDetail.ReserveAssetB.AssetIssuer = assetBIssuer
+	liquidityPoolDepositDetail.ReserveAssetB.AssetType = assetBType
+	liquidityPoolDepositDetail.ReserveAssetB.DepositAmount = int64(depositedB)
 
 	liquidityPoolDepositDetail.MinPrice, err = strconv.ParseFloat(op.MinPrice.String(), 64)
 	if err != nil {
diff --git a/ingest/liquidity_pool_withdraw_details.go b/ingest/liquidity_pool_withdraw_details.go
index 94d4a93ac7..44d750fbab 100644
--- a/ingest/liquidity_pool_withdraw_details.go
+++ b/ingest/liquidity_pool_withdraw_details.go
@@ -7,18 +7,10 @@ import (
 )
 
 type LiquidityPoolWithdrawDetail struct {
-	LiquidityPoolID        string `json:"liquidity_pool_id"`
-	ReserveAAssetCode      string `json:"reserve_a_asset_code"`
-	ReserveAAssetIssuer    string `json:"reserve_a_asset_issuer"`
-	ReserveAAssetType      string `json:"reserve_a_asset_type"`
-	ReserveAMinAmount      int64  `json:"reserve_a_min_amount,string"`
-	ReserveAWithdrawAmount int64  `json:"reserve_a_withdraw_amount,string"`
-	ReserveBAssetCode      string `json:"reserve_b_asset_code"`
-	ReserveBAssetIssuer    string `json:"reserve_b_asset_issuer"`
-	ReserveBAssetType      string `json:"reserve_b_asset_type"`
-	ReserveBMinAmount      int64  `json:"reserve_b_min_amount,string"`
-	ReserveBWithdrawAmount int64  `json:"reserve_b_withdraw_amount,string"`
-	Shares                 int64  `json:"shares,string"`
+	LiquidityPoolID string       `json:"liquidity_pool_id"`
+	ReserveAssetA   ReserveAsset `json:"reserve_asset_a"`
+	ReserveAssetB   ReserveAsset `json:"reserve_asset_b"`
+	Shares          int64        `json:"shares,string"`
 }
 
 func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (LiquidityPoolWithdrawDetail, error) {
@@ -28,9 +20,13 @@ func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (LiquidityPoolWithdrawD
 	}
 
 	liquidityPoolWithdrawDetail := LiquidityPoolWithdrawDetail{
-		ReserveAMinAmount: int64(op.MinAmountA),
-		ReserveBMinAmount: int64(op.MinAmountB),
-		Shares:            int64(op.Amount),
+		ReserveAssetA: ReserveAsset{
+			MinAmount: int64(op.MinAmountA),
+		},
+		ReserveAssetB: ReserveAsset{
+			MinAmount: int64(op.MinAmountB),
+		},
+		Shares: int64(op.Amount),
 	}
 
 	var err error
@@ -66,10 +62,10 @@ func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (LiquidityPoolWithdrawD
 		return LiquidityPoolWithdrawDetail{}, err
 	}
 
-	liquidityPoolWithdrawDetail.ReserveAAssetCode = assetACode
-	liquidityPoolWithdrawDetail.ReserveAAssetIssuer = assetAIssuer
-	liquidityPoolWithdrawDetail.ReserveAAssetType = assetAType
-	liquidityPoolWithdrawDetail.ReserveAWithdrawAmount = int64(receivedA)
+	liquidityPoolWithdrawDetail.ReserveAssetA.AssetCode = assetACode
+	liquidityPoolWithdrawDetail.ReserveAssetA.AssetIssuer = assetAIssuer
+	liquidityPoolWithdrawDetail.ReserveAssetA.AssetType = assetAType
+	liquidityPoolWithdrawDetail.ReserveAssetA.WithdrawAmount = int64(receivedA)
 
 	// Process AssetB Details
 	var assetBCode, assetBIssuer, assetBType string
@@ -78,10 +74,10 @@ func (o *LedgerOperation) LiquidityPoolWithdrawDetails() (LiquidityPoolWithdrawD
 		return LiquidityPoolWithdrawDetail{}, err
 	}
 
-	liquidityPoolWithdrawDetail.ReserveBAssetCode = assetBCode
-	liquidityPoolWithdrawDetail.ReserveBAssetIssuer = assetBIssuer
-	liquidityPoolWithdrawDetail.ReserveBAssetType = assetBType
-	liquidityPoolWithdrawDetail.ReserveBWithdrawAmount = int64(receivedB)
+	liquidityPoolWithdrawDetail.ReserveAssetB.AssetCode = assetBCode
+	liquidityPoolWithdrawDetail.ReserveAssetB.AssetIssuer = assetBIssuer
+	liquidityPoolWithdrawDetail.ReserveAssetB.AssetType = assetBType
+	liquidityPoolWithdrawDetail.ReserveAssetB.WithdrawAmount = int64(receivedB)
 
 	return liquidityPoolWithdrawDetail, nil
 }