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 new file mode 100644 index 0000000000..f75ed00d1a --- /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,string"` + Into string `json:"into"` + IntoMuxed string `json:"into_muxed"` + IntoMuxedID uint64 `json:"into_muxed_id,string"` +} + +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..3c3785c88b --- /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,string"` + 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).IsAuthorized(), + 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..abeb2a4e52 --- /dev/null +++ b/ingest/bump_sequence_details.go @@ -0,0 +1,18 @@ +package ingest + +import "fmt" + +type BumpSequenceDetails struct { + BumpTo int64 `json:"bump_to,string"` +} + +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: int64(op.BumpTo), + }, nil +} diff --git a/ingest/change_trust_details.go b/ingest/change_trust_details.go new file mode 100644 index 0000000000..ca3040bd2d --- /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,string"` + Trustee string `json:"trustee"` + Trustor string `json:"trustor"` + TrustorMuxed string `json:"trustor_muxed"` + TrustorMuxedID uint64 `json:"trustor_muxed_id,string"` +} + +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..69d85950ad --- /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,string"` +} + +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..ff84f41705 --- /dev/null +++ b/ingest/clawback_details.go @@ -0,0 +1,48 @@ +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,string"` + Amount int64 `json:"amount,string"` +} + +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), + From: op.From.Address(), + } + + 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..9eed63cc2d --- /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,string"` + Funder string `json:"funder"` + FunderMuxed string `json:"funder_muxed"` + FunderMuxedID uint64 `json:"funder_muxed_id,string"` +} + +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..0aefcf1ffb --- /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,string"` + 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..2ee8791b8c --- /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,string"` + 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..c2d6f609e3 --- /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,string"` +} + +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..69a74f8d16 --- /dev/null +++ b/ingest/invoke_host_function_details.go @@ -0,0 +1,137 @@ +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 []interface{} `json:"parameters"` + 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 + } + + invokeHostFunctionDetail.Parameters, err = o.serializeParameters(args) + if err != nil { + return InvokeHostFunctionDetail{}, err + } + + balanceChanges, err := o.parseAssetBalanceChangesFromContractEvents() + if err != nil { + return InvokeHostFunctionDetail{}, err + } + + invokeHostFunctionDetail.AssetBalanceChanges = balanceChanges + + case xdr.HostFunctionTypeHostFunctionTypeCreateContract: + args := op.HostFunction.MustCreateContract() + + invokeHostFunctionDetail.Type = "create_contract" + + preImageDetails, err := switchContractIdPreimageType(args.ContractIdPreimage) + if err != nil { + return InvokeHostFunctionDetail{}, nil + } + + o.getCreateContractDetails(&invokeHostFunctionDetail, preImageDetails) + 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" + + preImageDetails, err := switchContractIdPreimageType(args.ContractIdPreimage) + if err != nil { + return InvokeHostFunctionDetail{}, err + } + + 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, err = o.serializeParameters(args.ConstructorArgs) + if err != nil { + return InvokeHostFunctionDetail{}, err + } + 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 +} diff --git a/ingest/ledger_operation.go b/ingest/ledger_operation.go new file mode 100644 index 0000000000..981ac8bfb2 --- /dev/null +++ b/ingest/ledger_operation.go @@ -0,0 +1,619 @@ +package ingest + +import ( + "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" + "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() + return muxedAccount.ToAccountId().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 + 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() + if err != nil { + return "", err + } + return operationTraceCode, nil + } + } + + return operationTraceCode, nil +} + +func (o *LedgerOperation) OperationDetails() (interface{}, error) { + var err error + var details 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 getMuxedAccountDetails(a xdr.MuxedAccount) (string, uint64, error) { + var err error + var muxedAccountAddress string + var muxedAccountID uint64 + + if a.Type == xdr.CryptoKeyTypeKeyTypeMuxedEd25519 { + muxedAccountAddress, err = a.GetAddress() + if err != nil { + return "", 0, err + } + muxedAccountID, err = a.GetId() + if err != nil { + return "", 0, err + } + } + return muxedAccountAddress, muxedAccountID, 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,string"` + 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 addLedgerKeyToDetails(ledgerKey xdr.LedgerKey) (LedgerKeyDetail, error) { + var err error + var ledgerKeyDetail LedgerKeyDetail + + switch ledgerKey.Type { + case xdr.LedgerEntryTypeAccount: + ledgerKeyDetail.AccountID = ledgerKey.Account.AccountId.Address() + case xdr.LedgerEntryTypeClaimableBalance: + var marshalHex string + marshalHex, err = xdr.MarshalHex(ledgerKey.ClaimableBalance.BalanceId) + if err != nil { + return LedgerKeyDetail{}, fmt.Errorf("in claimable balance: %w", err) + } + ledgerKeyDetail.ClaimableBalanceID = marshalHex + case xdr.LedgerEntryTypeData: + ledgerKeyDetail.DataAccountID = ledgerKey.Data.AccountId.Address() + ledgerKeyDetail.DataName = string(ledgerKey.Data.DataName) + case xdr.LedgerEntryTypeOffer: + ledgerKeyDetail.OfferID = int64(ledgerKey.Offer.OfferId) + case xdr.LedgerEntryTypeTrustline: + ledgerKeyDetail.TrustlineAccountID = ledgerKey.TrustLine.AccountId.Address() + if ledgerKey.TrustLine.Asset.Type == xdr.AssetTypeAssetTypePoolShare { + ledgerKeyDetail.TrustlineLiquidityPoolID, err = PoolIDToString(*ledgerKey.TrustLine.Asset.LiquidityPoolId) + if err != nil { + return LedgerKeyDetail{}, err + } + } else { + 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: + ledgerKeyDetail.LiquidityPoolID, err = PoolIDToString(ledgerKey.LiquidityPool.LiquidityPoolId) + if err != nil { + return LedgerKeyDetail{}, err + } + } + + return ledgerKeyDetail, nil +} + +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) serializeParameters(args []xdr.ScVal) ([]interface{}, error) { + var params []interface{} + + for _, param := range args { + 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) + } + } + + return params, nil +} + +func (o *LedgerOperation) parseAssetBalanceChangesFromContractEvents() ([]BalanceChangeDetail, error) { + balanceChanges := []BalanceChangeDetail{} + + 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 + + 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 { + switch sacEvent.GetType() { + case contractevents.EventTypeTransfer: + transferEvt := sacEvent.(*contractevents.TransferEvent) + 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) + 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) + 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) + balanceChangeDetail, err = createSACBalanceChangeEntry(burnEvt.From, "", burnEvt.Amount, burnEvt.Asset, "burn") + if err != nil { + return []BalanceChangeDetail{}, err + } + + balanceChanges = append(balanceChanges, balanceChangeDetail) + } + } + } + + 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 +} + +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 +// 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 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 != "" { + balanceChangeDetail.From = fromAccount + } + if toAccount != "" { + balanceChangeDetail.To = toAccount + } + + var assetCode, assetIssuer, assetType string + err := asset.Extract(&assetType, &assetCode, &assetIssuer) + if err != nil { + return BalanceChangeDetail{}, err + } + + return balanceChangeDetail, nil +} + +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 PreImageDetails{}, err + } + return PreImageDetails{ + From: "address", + Address: address, + }, nil + case xdr.ContractIdPreimageTypeContractIdPreimageFromAsset: + var assetCode, assetIssuer, assetType string + contractIdPreimage.MustFromAsset().Extract(&assetType, &assetCode, &assetIssuer) + + return PreImageDetails{ + From: "asset", + AssetCode: assetCode, + AssetIssuer: assetIssuer, + AssetType: assetType, + }, nil + + default: + return PreImageDetails{}, fmt.Errorf("unknown contract id type: %s", contractIdPreimage.Type) + } +} + +func (o *LedgerOperation) ConvertStroopValueToReal(input 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 PoolIDToString(id xdr.PoolId) (string, error) { + return xdr.MarshalBase64(id) +} + +type Claimant struct { + Destination string `json:"destination"` + Predicate xdr.ClaimPredicate `json:"predicate"` +} + +func 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..74c6cb6d37 --- /dev/null +++ b/ingest/ledger_operation_test.go @@ -0,0 +1,1533 @@ +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), + 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{ + 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: 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 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: CreateAccountDetail{ + Account: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + Funder: "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", + FunderMuxed: "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", + FunderMuxedID: uint64(123), + StartingBalance: int64(25000000)}, + }, + { + err: nil, + result: PaymentDetail{ + Amount: int64(350000000), + AssetCode: "USDT", + AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + AssetType: "credit_alphanum4", + From: "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", + FromMuxed: "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", + FromMuxedID: uint64(123), + To: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"}, + }, + { + err: nil, + result: PaymentDetail{ + Amount: int64(350000000), + AssetType: "native", + From: "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", + FromMuxed: "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", + FromMuxedID: uint64(123), + To: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"}, + }, + { + err: nil, + result: PathPaymentStrictReceiveDetail{ + Amount: int64(8951495900), + AssetType: "native", + From: "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN", + Path: []Path{ + { + AssetCode: "USDT", + AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + AssetType: "credit_alphanum4", + }, + }, + SourceAmount: int64(0), + SourceAssetType: "native", + SourceMax: int64(8951495900), + To: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"}, + }, + { + err: nil, + 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: CreatePassiveSellOfferDetail{ + Amount: int64(631595000), + BuyingAssetCode: "USDT", + BuyingAssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + BuyingAssetType: "credit_alphanum4", + Price: 0.0791606, + PriceN: 99583200, + PriceD: 1257990000, + SellingAssetType: "native"}, + }, + { + err: nil, + 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: ChangeTrustDetail{ + AssetCode: "USSD", + AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + AssetType: "credit_alphanum4", + Limit: int64(500000000000000000), + Trustee: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + Trustor: "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", + TrustorMuxed: "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", + TrustorMuxedID: uint64(123)}, + }, + { + err: nil, + result: ChangeTrustDetail{ + AssetType: "liquidity_pool_shares", + Limit: int64(500000000000000000), + LiquidityPoolID: "HCYdbHWTAgSnO0gMMCCrUl6b5IzpPeYZTPafsG8HRS0=", + Trustor: "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", + TrustorMuxed: "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", + TrustorMuxedID: uint64(123)}, + }, + { + err: nil, + result: AllowTrustDetail{ + AssetCode: "USDT", + AssetIssuer: "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", + AssetType: "credit_alphanum4", + Authorize: true, + Trustee: "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", + TrusteeMuxed: "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", + TrusteeMuxedID: uint64(123), + Trustor: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"}, + }, + { + err: nil, + result: AccountMergeDetail{ + Account: "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", + AccountMuxed: "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", + AccountMuxedID: uint64(123), + Into: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"}, + }, + { + err: nil, + result: InflationDetail{}, + }, + { + err: nil, + result: ManageDataDetail{ + Name: "test", + Value: "dmFsdWU=", + }, + }, + { + err: nil, + result: BumpSequenceDetails{ + BumpTo: int64(100), + }, + }, + { + err: nil, + 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: PathPaymentStrictSendDetail{ + Amount: int64(640000000), + AssetType: "native", + DestinationMin: 4280460538, + From: "GAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAK", + FromMuxed: "MAISEMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMJ2I", + FromMuxedID: uint64(123), + Path: []Path{ + { + AssetCode: "USDT", + AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + AssetType: "credit_alphanum4", + }, + }, + SourceAmount: int64(1598182), + SourceAssetType: "native", + To: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA"}, + }, + { + err: nil, + result: CreateClaimableBalanceDetail{ + Amount: int64(1234567890000), + AssetCode: "USDT", + AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + AssetType: "credit_alphanum4", + 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: ClaimClaimableBalanceDetail{ + BalanceID: "AAAAAAECAwQFBgcICQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + Claimant: "GBT4YAEGJQ5YSFUMNKX6BPBUOCPNAIOFAVZOF6MIME2CECBMEIUXFZZN", + }, + }, + { + err: nil, + result: BeginSponsoringFutureReservesDetail{ + SponsoredID: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + }, + }, + { + err: nil, + result: RevokeSponsorshipDetail{ + SignerAccountID: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + SignerKey: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", + }, + }, + { + err: nil, + result: RevokeSponsorshipDetail{ + LedgerKeyDetails: LedgerKeyDetail{ + AccountID: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + }, + }, + }, + { + err: nil, + result: RevokeSponsorshipDetail{ + LedgerKeyDetails: LedgerKeyDetail{ + ClaimableBalanceID: "000000000102030405060708090000000000000000000000000000000000000000000000", + }, + }, + }, + { + err: nil, + result: RevokeSponsorshipDetail{ + LedgerKeyDetails: LedgerKeyDetail{ + DataAccountID: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + DataName: "test", + }, + }, + }, + { + err: nil, + result: RevokeSponsorshipDetail{ + LedgerKeyDetails: LedgerKeyDetail{ + OfferID: int64(100), + }, + }, + }, + { + err: nil, + result: RevokeSponsorshipDetail{ + LedgerKeyDetails: LedgerKeyDetail{ + TrustlineAccountID: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + TrustlineAssetCode: "USTT", + TrustlineAssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + TrustlineAssetType: "credit_alphanum4", + }, + }, + }, + { + err: nil, + result: RevokeSponsorshipDetail{ + LedgerKeyDetails: LedgerKeyDetail{ + LiquidityPoolID: "AQIDBAUGBwgJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + }, + }, + }, + { + err: nil, + result: ClawbackDetail{ + Amount: int64(1598182), + AssetCode: "USDT", + AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + AssetType: "credit_alphanum4", + From: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + }, + }, + { + err: nil, + result: ClawbackClaimableBalanceDetail{ + BalanceID: "AAAAAAECAwQFBgcICQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + }, + }, + { + err: nil, + 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: LiquidityPoolDepositDetail{ + 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=", + 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), + }, + }, + { + err: nil, + result: InvokeHostFunctionDetail{ + AssetBalanceChanges: []BalanceChangeDetail{}, + ContractID: "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37", + Function: "HostFunctionTypeHostFunctionTypeInvokeContract", + LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="}, + 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", + }, + }, + { + err: nil, + result: InvokeHostFunctionDetail{ + Address: "CAJDIVTYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABR37", + ContractID: "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W", + From: "address", + Function: "HostFunctionTypeHostFunctionTypeCreateContract", + LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="}, + Type: "create_contract", + }, + }, + { + err: nil, + result: InvokeHostFunctionDetail{ + AssetCode: "USDT", + AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + AssetType: "credit_alphanum4", + ContractID: "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W", + From: "asset", + Function: "HostFunctionTypeHostFunctionTypeCreateContract", + LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="}, + Type: "create_contract", + }, + }, + { + err: nil, + result: InvokeHostFunctionDetail{ + AssetCode: "USDT", + AssetIssuer: "GBVVRXLMNCJQW3IDDXC3X6XCH35B5Q7QXNMMFPENSOGUPQO7WO7HGZPA", + AssetType: "credit_alphanum4", + ContractID: "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W", + From: "asset", + Function: "HostFunctionTypeHostFunctionTypeCreateContractV2", + LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="}, + Parameters: []interface{}{ + json.RawMessage{0x7b, 0x22, 0x62, 0x6f, 0x6f, 0x6c, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x7d}, + }, + Type: "create_contract_v2", + }, + }, + { + err: nil, + result: InvokeHostFunctionDetail{ + Function: "HostFunctionTypeHostFunctionTypeUploadContractWasm", + LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="}, + Type: "upload_wasm", + }, + }, + { + err: nil, + result: ExtendFootprintTtlDetail{ + ExtendTo: uint32(1234), + LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="}, + ContractID: "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W", + Type: "extend_footprint_ttl", + }, + }, + { + err: nil, + result: RestoreFootprintDetail{ + LedgerKeyHash: []string{"AAAABgAAAAESNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=="}, + ContractID: "CAJDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT4W", + Type: "restore_footprint", + }, + }, + } + + return output +} diff --git a/ingest/ledger_transaction.go b/ingest/ledger_transaction.go index 07775cdae0..bbfa86da22 100644 --- a/ingest/ledger_transaction.go +++ b/ingest/ledger_transaction.go @@ -590,3 +590,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 := xdr.MarshalBase64(ledgerKey) + if err != nil { + panic(err) + } + if ledgerKeyBase64 != "" { + ledgerKeyHash = append(ledgerKeyHash, ledgerKeyBase64) + } + } + + for _, ledgerKey := range v1Envelope.Tx.Ext.SorobanData.Resources.Footprint.ReadWrite { + ledgerKeyBase64, err := xdr.MarshalBase64(ledgerKey) + 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 := xdr.MarshalBase64(contractCode.Hash) + 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/ingest/liquidity_pool_deposit_details.go b/ingest/liquidity_pool_deposit_details.go new file mode 100644 index 0000000000..d4d38635f8 --- /dev/null +++ b/ingest/liquidity_pool_deposit_details.go @@ -0,0 +1,118 @@ +package ingest + +import ( + "fmt" + "strconv" + + "github.com/stellar/go/xdr" +) + +type LiquidityPoolDepositDetail struct { + 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) { + 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{ + 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 + 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 + 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) + 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.ReserveAssetA.AssetCode = assetACode + liquidityPoolDepositDetail.ReserveAssetA.AssetIssuer = assetAIssuer + liquidityPoolDepositDetail.ReserveAssetA.AssetType = assetAType + liquidityPoolDepositDetail.ReserveAssetA.DepositAmount = int64(depositedA) + + //Process ReserveB Details + var assetBCode, assetBIssuer, assetBType string + err = assetB.Extract(&assetBType, &assetBCode, &assetBIssuer) + if err != nil { + return LiquidityPoolDepositDetail{}, err + } + + 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 { + 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..44d750fbab --- /dev/null +++ b/ingest/liquidity_pool_withdraw_details.go @@ -0,0 +1,83 @@ +package ingest + +import ( + "fmt" + + "github.com/stellar/go/xdr" +) + +type LiquidityPoolWithdrawDetail struct { + 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) { + 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{ + ReserveAssetA: ReserveAsset{ + MinAmount: int64(op.MinAmountA), + }, + ReserveAssetB: ReserveAsset{ + MinAmount: 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 + 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) + 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.ReserveAssetA.AssetCode = assetACode + liquidityPoolWithdrawDetail.ReserveAssetA.AssetIssuer = assetAIssuer + liquidityPoolWithdrawDetail.ReserveAssetA.AssetType = assetAType + liquidityPoolWithdrawDetail.ReserveAssetA.WithdrawAmount = int64(receivedA) + + // Process AssetB Details + var assetBCode, assetBIssuer, assetBType string + err = assetB.Extract(&assetBType, &assetBCode, &assetBIssuer) + if err != nil { + return LiquidityPoolWithdrawDetail{}, err + } + + liquidityPoolWithdrawDetail.ReserveAssetB.AssetCode = assetBCode + liquidityPoolWithdrawDetail.ReserveAssetB.AssetIssuer = assetBIssuer + liquidityPoolWithdrawDetail.ReserveAssetB.AssetType = assetBType + liquidityPoolWithdrawDetail.ReserveAssetB.WithdrawAmount = 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..81e39982a8 --- /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,string"` + Amount int64 `json:"amount,string"` + 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..ce52817130 --- /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..5fc7811ce1 --- /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,string"` + Amount int64 `json:"amount,string"` + 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..31dd64d514 --- /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,string"` + To string `json:"to"` + ToMuxed string `json:"to_muxed"` + 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,string"` + SourceAssetCode string `json:"source_asset_code"` + SourceAssetIssuer string `json:"source_asset_issuer"` + SourceAssetType string `json:"source_asset_type"` + SourceAmount int64 `json:"source_amount,string"` + SourceMax int64 `json:"source_max,string"` + 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..1a28b8d173 --- /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,string"` + To string `json:"to"` + ToMuxed string `json:"to_muxed"` + 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,string"` + SourceAssetCode string `json:"source_asset_code"` + SourceAssetIssuer string `json:"source_asset_issuer"` + SourceAssetType string `json:"source_asset_type"` + SourceAmount int64 `json:"source_amount,string"` + DestinationMin int64 `json:"destination_min,string"` + 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..c209db38ee --- /dev/null +++ b/ingest/payment_details.go @@ -0,0 +1,64 @@ +package ingest + +import ( + "fmt" +) + +type PaymentDetail struct { + From string `json:"from"` + FromMuxed string `json:"from_muxed"` + FromMuxedID uint64 `json:"from_muxed_id,string"` + To string `json:"to"` + ToMuxed string `json:"to_muxed"` + 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,string"` +} + +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 + } + + paymentDetail.AssetCode = assetCode + paymentDetail.AssetIssuer = assetIssuer + paymentDetail.AssetType = assetType + + 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..654d5f128e --- /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 +} 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 +}