diff --git a/csv/parsers/accointing/accointing.go b/csv/parsers/accointing/accointing.go index 298fcfb..88ed244 100644 --- a/csv/parsers/accointing/accointing.go +++ b/csv/parsers/accointing/accointing.go @@ -48,7 +48,7 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac // Parse all the TXs found in the Parsing Groups for _, txParsingGroup := range p.ParsingGroups { - err := txParsingGroup.ParseGroup(ParseGroup) + err := txParsingGroup.ParseGroup() if err != nil { return err } @@ -82,7 +82,7 @@ func (p *Parser) ProcessTaxableEvent(taxableEvents []db.TaxableEvent) error { } func (p *Parser) InitializeParsingGroups() { - p.ParsingGroups = append(p.ParsingGroups, parsers.GetOsmosisTxParsingGroups()...) + p.ParsingGroups = append(p.ParsingGroups, &OsmosisLpTxGroup{}, &OsmosisConcentratedLiquidityTxGroup{}) } func (p *Parser) GetRows(address string, startDate, endDate *time.Time) ([]parsers.CsvRow, error) { diff --git a/csv/parsers/accointing/osmosis.go b/csv/parsers/accointing/osmosis.go index 067f7e4..c0ab500 100644 --- a/csv/parsers/accointing/osmosis.go +++ b/csv/parsers/accointing/osmosis.go @@ -7,12 +7,42 @@ import ( "github.com/DefiantLabs/cosmos-tax-cli/config" "github.com/DefiantLabs/cosmos-tax-cli/csv/parsers" "github.com/DefiantLabs/cosmos-tax-cli/db" - "github.com/DefiantLabs/cosmos-tax-cli/util" + "github.com/DefiantLabs/cosmos-tax-cli/osmosis/modules/concentratedliquidity" "github.com/preichenberger/go-coinbasepro/v2" ) -func ParseGroup(sf *parsers.WrapperLpTxGroup) error { +type OsmosisLpTxGroup struct { + GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages + Rows []parsers.CsvRow +} + +func (sf *OsmosisLpTxGroup) GetRowsForParsingGroup() []parsers.CsvRow { + return sf.Rows +} + +func (sf *OsmosisLpTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { + _, isInGroup := parsers.IsOsmosisLpTxGroup[message.Message.MessageType.MessageType] + return isInGroup +} + +func (sf *OsmosisLpTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { + return sf.GroupedTxes +} + +func (sf *OsmosisLpTxGroup) String() string { + return "OsmosisLpTxGroup" +} + +func (sf *OsmosisLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { + // Add tx to group using the TX ID as key and appending to array + if sf.GroupedTxes == nil { + sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) + } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) +} + +func (sf *OsmosisLpTxGroup) ParseGroup() error { cbClient := coinbasepro.NewClient() for _, txMessages := range sf.GroupedTxes { for _, message := range txMessages { @@ -21,27 +51,8 @@ func ParseGroup(sf *parsers.WrapperLpTxGroup) error { row.OperationID = message.Message.Tx.Hash row.Date = message.Message.Tx.Block.TimeStamp.Format(TimeLayout) - denomRecieved := message.DenominationReceived - valueRecieved := message.AmountReceived - conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(valueRecieved), denomRecieved) - if err != nil { - row.InBuyAmount = util.NumericToString(valueRecieved) - row.InBuyAsset = denomRecieved.Base - } else { - row.InBuyAmount = conversionAmount.Text('f', -1) - row.InBuyAsset = conversionSymbol - } - - denomSent := message.DenominationSent - valueSent := message.AmountSent - conversionAmount, conversionSymbol, err = db.ConvertUnits(util.FromNumeric(valueSent), denomSent) - if err != nil { - row.OutSellAmount = util.NumericToString(valueSent) - row.OutSellAsset = denomSent.Base - } else { - row.OutSellAmount = conversionAmount.Text('f', -1) - row.OutSellAsset = conversionSymbol - } + parseAndAddReceivedAmountWithDefault(&row, message) + parseAndAddSentAmountWithDefault(&row, message) // We deliberately exclude the GAMM tokens from OutSell/InBuy for Exits/Joins respectively // Accointing has no way of using the GAMM token to determine LP cost basis etc... @@ -77,3 +88,59 @@ func ParseGroup(sf *parsers.WrapperLpTxGroup) error { } return nil } + +type OsmosisConcentratedLiquidityTxGroup struct { + GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages + Rows []parsers.CsvRow +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) GetRowsForParsingGroup() []parsers.CsvRow { + return sf.Rows +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { + _, isInGroup := parsers.IsOsmosisConcentratedLiquidity[message.Message.MessageType.MessageType] + return isInGroup +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { + return sf.GroupedTxes +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) String() string { + return "OsmosisLpTxGroup" +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) AddTxToGroup(tx db.TaxableTransaction) { + // Add tx to group using the TX ID as key and appending to array + if sf.GroupedTxes == nil { + sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) + } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) +} + +// Concentrated liquidit txs are grouped to be parsed together. Complex analysis may be require later, so group them now for later extension. +func (sf *OsmosisConcentratedLiquidityTxGroup) ParseGroup() error { + for _, txMessages := range sf.GroupedTxes { + for _, message := range txMessages { + row := Row{} + row.OperationID = message.Message.Tx.Hash + row.Date = message.Message.Tx.Block.TimeStamp.Format(TimeLayout) + + switch message.Message.MessageType.MessageType { + case concentratedliquidity.MsgCreatePosition: + parseAndAddSentAmountWithDefault(&row, message) + case concentratedliquidity.MsgWithdrawPosition, concentratedliquidity.MsgTransferPositions: + parseAndAddReceivedAmountWithDefault(&row, message) + case concentratedliquidity.MsgAddToPosition: + if message.DenominationReceivedID != nil { + parseAndAddReceivedAmountWithDefault(&row, message) + } else { + parseAndAddSentAmountWithDefault(&row, message) + } + } + sf.Rows = append(sf.Rows, row) + } + } + return nil +} diff --git a/csv/parsers/accointing/rows.go b/csv/parsers/accointing/rows.go index 369fc31..8497b42 100644 --- a/csv/parsers/accointing/rows.go +++ b/csv/parsers/accointing/rows.go @@ -110,3 +110,41 @@ func (row *Row) ParseFee(tx db.Tx, fee db.Fee) error { return nil } + +func parseAndAddSentAmount(row *Row, event db.TaxableTransaction) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(event.AmountSent), event.DenominationSent) + if err != nil { + return fmt.Errorf("cannot parse denom units for TX %s (classification: withdrawal)", row.OperationID) + } + row.OutSellAmount = conversionAmount.Text('f', -1) + row.OutSellAsset = conversionSymbol + + return nil +} + +func parseAndAddSentAmountWithDefault(row *Row, event db.TaxableTransaction) { + err := parseAndAddSentAmount(row, event) + if err != nil { + row.OutSellAmount = util.NumericToString(event.AmountSent) + row.OutSellAsset = event.DenominationSent.Base + } +} + +func parseAndAddReceivedAmount(row *Row, event db.TaxableTransaction) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(event.AmountReceived), event.DenominationReceived) + if err != nil { + return fmt.Errorf("cannot parse denom units for TX %s (classification: deposit)", row.OperationID) + } + row.InBuyAmount = conversionAmount.Text('f', -1) + row.InBuyAsset = conversionSymbol + + return nil +} + +func parseAndAddReceivedAmountWithDefault(row *Row, event db.TaxableTransaction) { + err := parseAndAddReceivedAmount(row, event) + if err != nil { + row.InBuyAmount = util.NumericToString(event.AmountReceived) + row.InBuyAsset = event.DenominationReceived.Base + } +} diff --git a/csv/parsers/cointracker/cointracker.go b/csv/parsers/cointracker/cointracker.go index 60f60f6..53f5220 100644 --- a/csv/parsers/cointracker/cointracker.go +++ b/csv/parsers/cointracker/cointracker.go @@ -60,7 +60,7 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac // Parse all the TXs found in the Parsing Groups for _, txParsingGroup := range p.ParsingGroups { - err := txParsingGroup.ParseGroup(ParseGroup) + err := txParsingGroup.ParseGroup() if err != nil { return err } @@ -94,7 +94,7 @@ func (p *Parser) ProcessTaxableEvent(taxableEvents []db.TaxableEvent) error { } func (p *Parser) InitializeParsingGroups() { - p.ParsingGroups = append(p.ParsingGroups, parsers.GetOsmosisTxParsingGroups()...) + p.ParsingGroups = append(p.ParsingGroups, &OsmosisLpTxGroup{}, &OsmosisConcentratedLiquidityTxGroup{}) } func (p *Parser) GetRows(address string, startDate, endDate *time.Time) ([]parsers.CsvRow, error) { diff --git a/csv/parsers/cointracker/osmosis.go b/csv/parsers/cointracker/osmosis.go index a5614da..da520a3 100644 --- a/csv/parsers/cointracker/osmosis.go +++ b/csv/parsers/cointracker/osmosis.go @@ -3,10 +3,40 @@ package cointracker import ( "github.com/DefiantLabs/cosmos-tax-cli/csv/parsers" "github.com/DefiantLabs/cosmos-tax-cli/db" + "github.com/DefiantLabs/cosmos-tax-cli/osmosis/modules/concentratedliquidity" "github.com/DefiantLabs/cosmos-tax-cli/util" ) -func ParseGroup(sf *parsers.WrapperLpTxGroup) error { +type OsmosisLpTxGroup struct { + GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages + Rows []parsers.CsvRow +} + +func (sf *OsmosisLpTxGroup) GetRowsForParsingGroup() []parsers.CsvRow { + return sf.Rows +} + +func (sf *OsmosisLpTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { + _, isInGroup := parsers.IsOsmosisLpTxGroup[message.Message.MessageType.MessageType] + return isInGroup +} + +func (sf *OsmosisLpTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { + return sf.GroupedTxes +} + +func (sf *OsmosisLpTxGroup) String() string { + return "OsmosisLpTxGroup" +} + +func (sf *OsmosisLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { + if sf.GroupedTxes == nil { + sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) + } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) +} + +func (sf *OsmosisLpTxGroup) ParseGroup() error { // cbClient := coinbasepro.NewClient() for _, txMessages := range sf.GroupedTxes { for _, message := range txMessages { @@ -74,3 +104,82 @@ func ParseGroup(sf *parsers.WrapperLpTxGroup) error { } return nil } + +type OsmosisConcentratedLiquidityTxGroup struct { + GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages + Rows []parsers.CsvRow +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) GetRowsForParsingGroup() []parsers.CsvRow { + return sf.Rows +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { + _, isInGroup := parsers.IsOsmosisConcentratedLiquidity[message.Message.MessageType.MessageType] + return isInGroup +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { + return sf.GroupedTxes +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) String() string { + return "OsmosisLpTxGroup" +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) AddTxToGroup(tx db.TaxableTransaction) { + // Add tx to group using the TX ID as key and appending to array + if sf.GroupedTxes == nil { + sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) + } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) +} + +// Concentrated liquidit txs are grouped to be parsed together. Complex analysis may be require later, so group them now for later extension. +func (sf *OsmosisConcentratedLiquidityTxGroup) ParseGroup() error { + txsToFees := parsers.GetTxToFeesMap(sf.GroupedTxes) + for _, txMessages := range sf.GroupedTxes { + for _, message := range txMessages { + + row := Row{} + row.Date = message.Message.Tx.Block.TimeStamp.Format(TimeLayout) + + switch message.Message.MessageType.MessageType { + case concentratedliquidity.MsgCreatePosition: + parseAndAddSentAmountWithDefault(&row, message) + case concentratedliquidity.MsgWithdrawPosition, concentratedliquidity.MsgTransferPositions: + parseAndAddReceivedAmountWithDefault(&row, message) + case concentratedliquidity.MsgAddToPosition: + if message.DenominationReceivedID != nil { + parseAndAddReceivedAmountWithDefault(&row, message) + } else { + parseAndAddSentAmountWithDefault(&row, message) + } + } + + messageFee := txsToFees[message.Message.Tx.ID] + if len(messageFee) > 0 { + fee := messageFee[0] + parseAndAddFeeWithDefault(&row, fee) + + // This fee has been processed, pop it off the stack + txsToFees[message.Message.Tx.ID] = txsToFees[message.Message.Tx.ID][1:] + + } + sf.Rows = append(sf.Rows, row) + } + } + + // If there are any fees left over, add them to the CSV + for _, fees := range txsToFees { + for _, fee := range fees { + row := Row{} + err := row.ParseFee(fee.Tx, fee) + if err != nil { + return err + } + sf.Rows = append(sf.Rows, row) + } + } + return nil +} diff --git a/csv/parsers/cointracker/rows.go b/csv/parsers/cointracker/rows.go index 9d87206..258a3e1 100644 --- a/csv/parsers/cointracker/rows.go +++ b/csv/parsers/cointracker/rows.go @@ -1,6 +1,7 @@ package cointracker import ( + "errors" "fmt" "github.com/DefiantLabs/cosmos-tax-cli/db" @@ -103,3 +104,60 @@ func (row *Row) ParseFee(tx db.Tx, fee db.Fee) error { return nil } + +func parseAndAddSentAmount(row *Row, event db.TaxableTransaction) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(event.AmountSent), event.DenominationSent) + if err != nil { + return errors.New("cannot parse denom units") + } + row.SentAmount = conversionAmount.Text('f', -1) + row.SentCurrency = conversionSymbol + + return nil +} + +func parseAndAddSentAmountWithDefault(row *Row, event db.TaxableTransaction) { + err := parseAndAddSentAmount(row, event) + if err != nil { + row.SentAmount = util.NumericToString(event.AmountSent) + row.SentCurrency = event.DenominationSent.Base + } +} + +func parseAndAddReceivedAmount(row *Row, event db.TaxableTransaction) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(event.AmountReceived), event.DenominationReceived) + if err != nil { + return errors.New("cannot parse denom units") + } + row.ReceivedAmount = conversionAmount.Text('f', -1) + row.ReceivedCurrency = conversionSymbol + + return nil +} + +func parseAndAddReceivedAmountWithDefault(row *Row, event db.TaxableTransaction) { + err := parseAndAddReceivedAmount(row, event) + if err != nil { + row.ReceivedAmount = util.NumericToString(event.AmountReceived) + row.ReceivedCurrency = event.DenominationReceived.Base + } +} + +func parseAndAddFee(row *Row, fee db.Fee) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(fee.Amount), fee.Denomination) + if err != nil { + return errors.New("cannot parse denom units") + } + row.FeeAmount = conversionAmount.Text('f', -1) + row.FeeCurrency = conversionSymbol + + return nil +} + +func parseAndAddFeeWithDefault(row *Row, fee db.Fee) { + err := parseAndAddFee(row, fee) + if err != nil { + row.FeeAmount = util.NumericToString(fee.Amount) + row.FeeCurrency = fee.Denomination.Base + } +} diff --git a/csv/parsers/cryptotaxcalculator/cryptotaxcalculator.go b/csv/parsers/cryptotaxcalculator/cryptotaxcalculator.go index 0296211..c1d3bea 100644 --- a/csv/parsers/cryptotaxcalculator/cryptotaxcalculator.go +++ b/csv/parsers/cryptotaxcalculator/cryptotaxcalculator.go @@ -55,7 +55,7 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac // Parse all the TXs found in the Parsing Groups for _, txParsingGroup := range p.ParsingGroups { - err := txParsingGroup.ParseGroup(ParseGroup) + err := txParsingGroup.ParseGroup() if err != nil { return err } @@ -63,7 +63,7 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac for _, fee := range feesWithoutTx { row := Row{} - err := row.ParseFee(address, fee) + err := row.ParseFee(fee) if err != nil { return err } @@ -369,7 +369,7 @@ func ParseMsgRecvPacket(address string, event db.TaxableTransaction) (Row, error } func (p *Parser) InitializeParsingGroups() { - p.ParsingGroups = append(p.ParsingGroups, parsers.GetOsmosisTxParsingGroups()...) + p.ParsingGroups = append(p.ParsingGroups, &OsmosisLpTxGroup{}, &OsmosisConcentratedLiquidityTxGroup{}) } func ParseOsmosisReward(event db.TaxableEvent) (Row, error) { diff --git a/csv/parsers/cryptotaxcalculator/osmosis.go b/csv/parsers/cryptotaxcalculator/osmosis.go index b72649e..b2ab857 100644 --- a/csv/parsers/cryptotaxcalculator/osmosis.go +++ b/csv/parsers/cryptotaxcalculator/osmosis.go @@ -3,11 +3,41 @@ package cryptotaxcalculator import ( "github.com/DefiantLabs/cosmos-tax-cli/csv/parsers" "github.com/DefiantLabs/cosmos-tax-cli/db" + "github.com/DefiantLabs/cosmos-tax-cli/osmosis/modules/concentratedliquidity" "github.com/DefiantLabs/cosmos-tax-cli/osmosis/modules/gamm" "github.com/DefiantLabs/cosmos-tax-cli/util" ) -func ParseGroup(sf *parsers.WrapperLpTxGroup) error { +type OsmosisLpTxGroup struct { + GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages + Rows []parsers.CsvRow +} + +func (sf *OsmosisLpTxGroup) GetRowsForParsingGroup() []parsers.CsvRow { + return sf.Rows +} + +func (sf *OsmosisLpTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { + _, isInGroup := parsers.IsOsmosisLpTxGroup[message.Message.MessageType.MessageType] + return isInGroup +} + +func (sf *OsmosisLpTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { + return sf.GroupedTxes +} + +func (sf *OsmosisLpTxGroup) String() string { + return "OsmosisLpTxGroup" +} + +func (sf *OsmosisLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { + if sf.GroupedTxes == nil { + sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) + } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) +} + +func (sf *OsmosisLpTxGroup) ParseGroup() error { for _, txMessages := range sf.GroupedTxes { for _, message := range txMessages { row := Row{} @@ -49,3 +79,86 @@ func ParseGroup(sf *parsers.WrapperLpTxGroup) error { } return nil } + +type OsmosisConcentratedLiquidityTxGroup struct { + GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages + Rows []parsers.CsvRow +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) GetRowsForParsingGroup() []parsers.CsvRow { + return sf.Rows +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { + _, isInGroup := parsers.IsOsmosisConcentratedLiquidity[message.Message.MessageType.MessageType] + return isInGroup +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { + return sf.GroupedTxes +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) String() string { + return "OsmosisLpTxGroup" +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) AddTxToGroup(tx db.TaxableTransaction) { + // Add tx to group using the TX ID as key and appending to array + if sf.GroupedTxes == nil { + sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) + } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) +} + +// Concentrated liquidit txs are grouped to be parsed together. Complex analysis may be require later, so group them now for later extension. +func (sf *OsmosisConcentratedLiquidityTxGroup) ParseGroup() error { + txsToFees := parsers.GetTxToFeesMap(sf.GroupedTxes) + for _, txMessages := range sf.GroupedTxes { + for _, message := range txMessages { + + row := Row{} + row.Date = message.Message.Tx.Block.TimeStamp.Format(TimeLayout) + row.ID = message.Message.Tx.Hash + switch message.Message.MessageType.MessageType { + case concentratedliquidity.MsgCreatePosition: + parseAndAddSentAmountWithDefault(&row, message) + row.Type = Sell + case concentratedliquidity.MsgWithdrawPosition, concentratedliquidity.MsgTransferPositions: + parseAndAddReceivedAmountWithDefault(&row, message) + row.Type = Buy + case concentratedliquidity.MsgAddToPosition: + if message.DenominationReceivedID != nil { + parseAndAddReceivedAmountWithDefault(&row, message) + row.Type = Buy + } else { + parseAndAddSentAmountWithDefault(&row, message) + row.Type = Sell + } + } + + messageFee := txsToFees[message.Message.Tx.ID] + if len(messageFee) > 0 { + fee := messageFee[0] + parseAndAddFeeWithDefault(&row, fee) + + // This fee has been processed, pop it off the stack + txsToFees[message.Message.Tx.ID] = txsToFees[message.Message.Tx.ID][1:] + + } + sf.Rows = append(sf.Rows, row) + } + } + + // If there are any fees left over, add them to the CSV + for _, fees := range txsToFees { + for _, fee := range fees { + row := Row{} + err := row.ParseFee(fee) + if err != nil { + return err + } + sf.Rows = append(sf.Rows, row) + } + } + return nil +} diff --git a/csv/parsers/cryptotaxcalculator/rows.go b/csv/parsers/cryptotaxcalculator/rows.go index 4b4705c..5cc0de1 100644 --- a/csv/parsers/cryptotaxcalculator/rows.go +++ b/csv/parsers/cryptotaxcalculator/rows.go @@ -1,6 +1,7 @@ package cryptotaxcalculator import ( + "errors" "fmt" "github.com/DefiantLabs/cosmos-tax-cli/db" @@ -87,7 +88,7 @@ func (row *Row) ParseBasic(address string, event db.TaxableTransaction) error { return nil } -func (row *Row) ParseFee(address string, fee db.Fee) error { +func (row *Row) ParseFee(fee db.Fee) error { row.Date = fee.Tx.Block.TimeStamp.Format(TimeLayout) row.ID = fee.Tx.Hash row.Type = Fee @@ -138,3 +139,60 @@ func (row *Row) ParseSwap(event db.TaxableTransaction, address, eventType string return nil } + +func parseAndAddSentAmount(row *Row, event db.TaxableTransaction) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(event.AmountSent), event.DenominationSent) + if err != nil { + return errors.New("cannot parse denom units") + } + row.QuoteAmount = conversionAmount.Text('f', -1) + row.QuoteCurrency = conversionSymbol + + return nil +} + +func parseAndAddSentAmountWithDefault(row *Row, event db.TaxableTransaction) { + err := parseAndAddSentAmount(row, event) + if err != nil { + row.QuoteAmount = util.NumericToString(event.AmountSent) + row.QuoteCurrency = event.DenominationSent.Base + } +} + +func parseAndAddReceivedAmount(row *Row, event db.TaxableTransaction) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(event.AmountReceived), event.DenominationReceived) + if err != nil { + return errors.New("cannot parse denom units") + } + row.BaseAmount = conversionAmount.Text('f', -1) + row.BaseCurrency = conversionSymbol + + return nil +} + +func parseAndAddReceivedAmountWithDefault(row *Row, event db.TaxableTransaction) { + err := parseAndAddReceivedAmount(row, event) + if err != nil { + row.BaseAmount = util.NumericToString(event.AmountReceived) + row.BaseCurrency = event.DenominationReceived.Base + } +} + +func parseAndAddFee(row *Row, fee db.Fee) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(fee.Amount), fee.Denomination) + if err != nil { + return errors.New("cannot parse denom units") + } + row.FeeAmount = conversionAmount.Text('f', -1) + row.FeeCurrency = conversionSymbol + + return nil +} + +func parseAndAddFeeWithDefault(row *Row, fee db.Fee) { + err := parseAndAddFee(row, fee) + if err != nil { + row.FeeAmount = util.NumericToString(fee.Amount) + row.FeeCurrency = fee.Denomination.Base + } +} diff --git a/csv/parsers/koinly/koinly.go b/csv/parsers/koinly/koinly.go index 0fc20c7..005fc25 100644 --- a/csv/parsers/koinly/koinly.go +++ b/csv/parsers/koinly/koinly.go @@ -73,7 +73,7 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac // Parse all the TXs found in the Parsing Groups for _, txParsingGroup := range p.ParsingGroups { - err := txParsingGroup.ParseGroup(ParseGroup) + err := txParsingGroup.ParseGroup() if err != nil { return err } @@ -107,7 +107,7 @@ func (p *Parser) ProcessTaxableEvent(taxableEvents []db.TaxableEvent) error { } func (p *Parser) InitializeParsingGroups() { - p.ParsingGroups = append(p.ParsingGroups, parsers.GetOsmosisTxParsingGroups()...) + p.ParsingGroups = append(p.ParsingGroups, &OsmosisLpTxGroup{}, &OsmosisConcentratedLiquidityTxGroup{}) } func (p *Parser) GetRows(address string, startDate, endDate *time.Time) ([]parsers.CsvRow, error) { diff --git a/csv/parsers/koinly/osmosis.go b/csv/parsers/koinly/osmosis.go index 2f62396..c250edb 100644 --- a/csv/parsers/koinly/osmosis.go +++ b/csv/parsers/koinly/osmosis.go @@ -7,12 +7,42 @@ import ( "github.com/DefiantLabs/cosmos-tax-cli/config" "github.com/DefiantLabs/cosmos-tax-cli/csv/parsers" "github.com/DefiantLabs/cosmos-tax-cli/db" + "github.com/DefiantLabs/cosmos-tax-cli/osmosis/modules/concentratedliquidity" "github.com/DefiantLabs/cosmos-tax-cli/util" "github.com/preichenberger/go-coinbasepro/v2" ) -func ParseGroup(sf *parsers.WrapperLpTxGroup) error { +type OsmosisLpTxGroup struct { + GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages + Rows []parsers.CsvRow +} + +func (sf *OsmosisLpTxGroup) GetRowsForParsingGroup() []parsers.CsvRow { + return sf.Rows +} + +func (sf *OsmosisLpTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { + _, isInGroup := parsers.IsOsmosisLpTxGroup[message.Message.MessageType.MessageType] + return isInGroup +} + +func (sf *OsmosisLpTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { + return sf.GroupedTxes +} + +func (sf *OsmosisLpTxGroup) String() string { + return "OsmosisLpTxGroup" +} + +func (sf *OsmosisLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { + if sf.GroupedTxes == nil { + sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) + } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) +} + +func (sf *OsmosisLpTxGroup) ParseGroup() error { cbClient := coinbasepro.NewClient() for _, txMessages := range sf.GroupedTxes { for _, message := range txMessages { @@ -78,3 +108,86 @@ func ParseGroup(sf *parsers.WrapperLpTxGroup) error { } return nil } + +type OsmosisConcentratedLiquidityTxGroup struct { + GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages + Rows []parsers.CsvRow +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) GetRowsForParsingGroup() []parsers.CsvRow { + return sf.Rows +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { + _, isInGroup := parsers.IsOsmosisConcentratedLiquidity[message.Message.MessageType.MessageType] + return isInGroup +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { + return sf.GroupedTxes +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) String() string { + return "OsmosisLpTxGroup" +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) AddTxToGroup(tx db.TaxableTransaction) { + // Add tx to group using the TX ID as key and appending to array + if sf.GroupedTxes == nil { + sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) + } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) +} + +// Concentrated liquidit txs are grouped to be parsed together. Complex analysis may be require later, so group them now for later extension. +func (sf *OsmosisConcentratedLiquidityTxGroup) ParseGroup() error { + txsToFees := parsers.GetTxToFeesMap(sf.GroupedTxes) + for _, txMessages := range sf.GroupedTxes { + for _, message := range txMessages { + + row := Row{} + row.Date = message.Message.Tx.Block.TimeStamp.Format(TimeLayout) + row.TxHash = message.Message.Tx.Hash + switch message.Message.MessageType.MessageType { + case concentratedliquidity.MsgCreatePosition: + parseAndAddSentAmountWithDefault(&row, message) + row.Label = LiquidityIn + case concentratedliquidity.MsgWithdrawPosition, concentratedliquidity.MsgTransferPositions: + parseAndAddReceivedAmountWithDefault(&row, message) + row.Label = LiquidityOut + case concentratedliquidity.MsgAddToPosition: + if message.DenominationReceivedID != nil { + parseAndAddReceivedAmountWithDefault(&row, message) + row.Label = LiquidityIn + } else { + parseAndAddSentAmountWithDefault(&row, message) + row.Label = LiquidityOut + } + } + + messageFee := txsToFees[message.Message.Tx.ID] + if len(messageFee) > 0 { + fee := messageFee[0] + parseAndAddFeeWithDefault(&row, fee) + + // This fee has been processed, pop it off the stack + txsToFees[message.Message.Tx.ID] = txsToFees[message.Message.Tx.ID][1:] + + } + sf.Rows = append(sf.Rows, row) + } + } + + // If there are any fees left over, add them to the CSV + for _, fees := range txsToFees { + for _, fee := range fees { + row := Row{} + err := row.ParseFee(fee.Tx, fee) + if err != nil { + return err + } + sf.Rows = append(sf.Rows, row) + } + } + return nil +} diff --git a/csv/parsers/koinly/rows.go b/csv/parsers/koinly/rows.go index c4c04a5..218ae30 100644 --- a/csv/parsers/koinly/rows.go +++ b/csv/parsers/koinly/rows.go @@ -1,6 +1,7 @@ package koinly import ( + "errors" "fmt" "github.com/DefiantLabs/cosmos-tax-cli/db" @@ -110,3 +111,60 @@ func (row *Row) ParseFee(tx db.Tx, fee db.Fee) error { return nil } + +func parseAndAddSentAmount(row *Row, event db.TaxableTransaction) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(event.AmountSent), event.DenominationSent) + if err != nil { + return fmt.Errorf("cannot parse denom units") + } + row.SentAmount = conversionAmount.Text('f', -1) + row.SentCurrency = conversionSymbol + + return nil +} + +func parseAndAddSentAmountWithDefault(row *Row, event db.TaxableTransaction) { + err := parseAndAddSentAmount(row, event) + if err != nil { + row.SentAmount = util.NumericToString(event.AmountSent) + row.SentCurrency = event.DenominationSent.Base + } +} + +func parseAndAddReceivedAmount(row *Row, event db.TaxableTransaction) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(event.AmountReceived), event.DenominationReceived) + if err != nil { + return fmt.Errorf("cannot parse denom units") + } + row.ReceivedAmount = conversionAmount.Text('f', -1) + row.ReceivedCurrency = conversionSymbol + + return nil +} + +func parseAndAddReceivedAmountWithDefault(row *Row, event db.TaxableTransaction) { + err := parseAndAddReceivedAmount(row, event) + if err != nil { + row.ReceivedAmount = util.NumericToString(event.AmountReceived) + row.ReceivedCurrency = event.DenominationReceived.Base + } +} + +func parseAndAddFee(row *Row, fee db.Fee) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(fee.Amount), fee.Denomination) + if err != nil { + return errors.New("cannot parse denom units") + } + row.FeeAmount = conversionAmount.Text('f', -1) + row.FeeCurrency = conversionSymbol + + return nil +} + +func parseAndAddFeeWithDefault(row *Row, fee db.Fee) { + err := parseAndAddFee(row, fee) + if err != nil { + row.FeeAmount = util.NumericToString(fee.Amount) + row.FeeCurrency = fee.Denomination.Base + } +} diff --git a/csv/parsers/osmosis.go b/csv/parsers/osmosis.go index 18ba5cb..c13e895 100644 --- a/csv/parsers/osmosis.go +++ b/csv/parsers/osmosis.go @@ -1,7 +1,7 @@ package parsers import ( - "github.com/DefiantLabs/cosmos-tax-cli/db" + "github.com/DefiantLabs/cosmos-tax-cli/osmosis/modules/concentratedliquidity" "github.com/DefiantLabs/cosmos-tax-cli/osmosis/modules/gamm" ) @@ -17,6 +17,13 @@ var IsOsmosisExit = map[string]bool{ gamm.MsgExitPool: true, } +var IsOsmosisConcentratedLiquidity = map[string]bool{ + concentratedliquidity.MsgAddToPosition: true, + concentratedliquidity.MsgWithdrawPosition: true, + concentratedliquidity.MsgCreatePosition: true, + concentratedliquidity.MsgTransferPositions: true, +} + // IsOsmosisLpTxGroup is used as a guard for adding messages to the group. var IsOsmosisLpTxGroup = make(map[string]bool) @@ -29,53 +36,3 @@ func init() { IsOsmosisLpTxGroup[messageType] = true } } - -type WrapperLpTxGroup struct { - GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages - Rows []CsvRow -} - -func (sf *WrapperLpTxGroup) ParseGroup(parsingFunc func(sf *WrapperLpTxGroup) error) error { - return parsingFunc(sf) -} - -func (sf *WrapperLpTxGroup) GetRowsForParsingGroup() []CsvRow { - return sf.Rows -} - -func (sf *WrapperLpTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { - _, isInGroup := IsOsmosisLpTxGroup[message.Message.MessageType.MessageType] - return isInGroup -} - -func (sf *WrapperLpTxGroup) String() string { - return "OsmosisLpTxGroup" -} - -func (sf *WrapperLpTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { - return sf.GroupedTxes -} - -func (sf *WrapperLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { - // Add tx to group using the TX ID as key and appending to array - if _, ok := sf.GroupedTxes[tx.Message.Tx.ID]; ok { - sf.GroupedTxes[tx.Message.Tx.ID] = append(sf.GroupedTxes[tx.Message.Tx.ID], tx) - } else { - var txGrouping []db.TaxableTransaction - txGrouping = append(txGrouping, tx) - sf.GroupedTxes[tx.Message.Tx.ID] = txGrouping - } -} - -func GetOsmosisTxParsingGroups() []ParsingGroup { - var messageGroups []ParsingGroup - - // This appending of parsing groups establishes a precedence - // There is a break statement in the loop doing grouping - // Which means parsers further up the array will be preferred - LpTxGroup := WrapperLpTxGroup{} - LpTxGroup.GroupedTxes = make(map[uint][]db.TaxableTransaction) - messageGroups = append(messageGroups, &LpTxGroup) - - return messageGroups -} diff --git a/csv/parsers/taxbit/osmosis.go b/csv/parsers/taxbit/osmosis.go index 7e5b987..9fb5f69 100644 --- a/csv/parsers/taxbit/osmosis.go +++ b/csv/parsers/taxbit/osmosis.go @@ -3,10 +3,40 @@ package taxbit import ( "github.com/DefiantLabs/cosmos-tax-cli/csv/parsers" "github.com/DefiantLabs/cosmos-tax-cli/db" + "github.com/DefiantLabs/cosmos-tax-cli/osmosis/modules/concentratedliquidity" "github.com/DefiantLabs/cosmos-tax-cli/util" ) -func ParseGroup(sf *parsers.WrapperLpTxGroup) error { +type OsmosisLpTxGroup struct { + GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages + Rows []parsers.CsvRow +} + +func (sf *OsmosisLpTxGroup) GetRowsForParsingGroup() []parsers.CsvRow { + return sf.Rows +} + +func (sf *OsmosisLpTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { + _, isInGroup := parsers.IsOsmosisLpTxGroup[message.Message.MessageType.MessageType] + return isInGroup +} + +func (sf *OsmosisLpTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { + return sf.GroupedTxes +} + +func (sf *OsmosisLpTxGroup) String() string { + return "OsmosisLpTxGroup" +} + +func (sf *OsmosisLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { + if sf.GroupedTxes == nil { + sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) + } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) +} + +func (sf *OsmosisLpTxGroup) ParseGroup() error { // cbClient := coinbasepro.NewClient() for _, txMessages := range sf.GroupedTxes { for _, message := range txMessages { @@ -74,3 +104,86 @@ func ParseGroup(sf *parsers.WrapperLpTxGroup) error { } return nil } + +type OsmosisConcentratedLiquidityTxGroup struct { + GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages + Rows []parsers.CsvRow +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) GetRowsForParsingGroup() []parsers.CsvRow { + return sf.Rows +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) BelongsToGroup(message db.TaxableTransaction) bool { + _, isInGroup := parsers.IsOsmosisConcentratedLiquidity[message.Message.MessageType.MessageType] + return isInGroup +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) GetGroupedTxes() map[uint][]db.TaxableTransaction { + return sf.GroupedTxes +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) String() string { + return "OsmosisLpTxGroup" +} + +func (sf *OsmosisConcentratedLiquidityTxGroup) AddTxToGroup(tx db.TaxableTransaction) { + // Add tx to group using the TX ID as key and appending to array + if sf.GroupedTxes == nil { + sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) + } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) +} + +// Concentrated liquidit txs are grouped to be parsed together. Complex analysis may be require later, so group them now for later extension. +func (sf *OsmosisConcentratedLiquidityTxGroup) ParseGroup() error { + txsToFees := parsers.GetTxToFeesMap(sf.GroupedTxes) + for _, txMessages := range sf.GroupedTxes { + for _, message := range txMessages { + + row := Row{} + row.Date = message.Message.Tx.Block.TimeStamp.Format(TimeLayout) + row.TxHash = message.Message.Tx.Hash + switch message.Message.MessageType.MessageType { + case concentratedliquidity.MsgCreatePosition: + parseAndAddSentAmountWithDefault(&row, message) + row.TransactionType = Sale + case concentratedliquidity.MsgWithdrawPosition, concentratedliquidity.MsgTransferPositions: + parseAndAddReceivedAmountWithDefault(&row, message) + row.TransactionType = Buy + case concentratedliquidity.MsgAddToPosition: + if message.DenominationReceivedID != nil { + parseAndAddReceivedAmountWithDefault(&row, message) + row.TransactionType = Buy + } else { + parseAndAddSentAmountWithDefault(&row, message) + row.TransactionType = Sale + } + } + + messageFee := txsToFees[message.Message.Tx.ID] + if len(messageFee) > 0 { + fee := messageFee[0] + parseAndAddFeeWithDefault(&row, fee) + + // This fee has been processed, pop it off the stack + txsToFees[message.Message.Tx.ID] = txsToFees[message.Message.Tx.ID][1:] + + } + sf.Rows = append(sf.Rows, row) + } + } + + // If there are any fees left over, add them to the CSV + for _, fees := range txsToFees { + for _, fee := range fees { + row := Row{} + err := row.ParseFee(fee.Tx, fee) + if err != nil { + return err + } + sf.Rows = append(sf.Rows, row) + } + } + return nil +} diff --git a/csv/parsers/taxbit/rows.go b/csv/parsers/taxbit/rows.go index 72c5d7c..def7090 100644 --- a/csv/parsers/taxbit/rows.go +++ b/csv/parsers/taxbit/rows.go @@ -1,6 +1,7 @@ package taxbit import ( + "errors" "fmt" "github.com/DefiantLabs/cosmos-tax-cli/db" @@ -120,3 +121,60 @@ func (row *Row) ParseFee(tx db.Tx, fee db.Fee) error { return nil } + +func parseAndAddSentAmount(row *Row, event db.TaxableTransaction) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(event.AmountSent), event.DenominationSent) + if err != nil { + return fmt.Errorf("cannot parse denom units") + } + row.SentAmount = conversionAmount.Text('f', -1) + row.SentCurrency = conversionSymbol + + return nil +} + +func parseAndAddSentAmountWithDefault(row *Row, event db.TaxableTransaction) { + err := parseAndAddSentAmount(row, event) + if err != nil { + row.SentAmount = util.NumericToString(event.AmountSent) + row.SentCurrency = event.DenominationSent.Base + } +} + +func parseAndAddReceivedAmount(row *Row, event db.TaxableTransaction) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(event.AmountReceived), event.DenominationReceived) + if err != nil { + return fmt.Errorf("cannot parse denom units") + } + row.ReceivedAmount = conversionAmount.Text('f', -1) + row.ReceivedCurrency = conversionSymbol + + return nil +} + +func parseAndAddReceivedAmountWithDefault(row *Row, event db.TaxableTransaction) { + err := parseAndAddReceivedAmount(row, event) + if err != nil { + row.ReceivedAmount = util.NumericToString(event.AmountReceived) + row.ReceivedCurrency = event.DenominationReceived.Base + } +} + +func parseAndAddFee(row *Row, fee db.Fee) error { + conversionAmount, conversionSymbol, err := db.ConvertUnits(util.FromNumeric(fee.Amount), fee.Denomination) + if err != nil { + return errors.New("cannot parse denom units") + } + row.FeeAmount = conversionAmount.Text('f', -1) + row.FeeCurrency = conversionSymbol + + return nil +} + +func parseAndAddFeeWithDefault(row *Row, fee db.Fee) { + err := parseAndAddFee(row, fee) + if err != nil { + row.FeeAmount = util.NumericToString(fee.Amount) + row.FeeCurrency = fee.Denomination.Base + } +} diff --git a/csv/parsers/taxbit/taxbit.go b/csv/parsers/taxbit/taxbit.go index d460dc7..0ad26e1 100644 --- a/csv/parsers/taxbit/taxbit.go +++ b/csv/parsers/taxbit/taxbit.go @@ -45,7 +45,7 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac // Parse all the TXs found in the Parsing Groups for _, txParsingGroup := range p.ParsingGroups { - err := txParsingGroup.ParseGroup(ParseGroup) + err := txParsingGroup.ParseGroup() if err != nil { return err } @@ -79,7 +79,7 @@ func (p *Parser) ProcessTaxableEvent(taxableEvents []db.TaxableEvent) error { } func (p *Parser) InitializeParsingGroups() { - p.ParsingGroups = append(p.ParsingGroups, parsers.GetOsmosisTxParsingGroups()...) + p.ParsingGroups = append(p.ParsingGroups, &OsmosisLpTxGroup{}, &OsmosisConcentratedLiquidityTxGroup{}) } func (p *Parser) GetRows(address string, startDate, endDate *time.Time) ([]parsers.CsvRow, error) { diff --git a/csv/parsers/types.go b/csv/parsers/types.go index b1191d7..19e9845 100644 --- a/csv/parsers/types.go +++ b/csv/parsers/types.go @@ -20,7 +20,7 @@ type ParsingGroup interface { String() string AddTxToGroup(db.TaxableTransaction) GetGroupedTxes() map[uint][]db.TaxableTransaction - ParseGroup(parsingFunc func(sf *WrapperLpTxGroup) error) error + ParseGroup() error GetRowsForParsingGroup() []CsvRow } diff --git a/csv/parsers/utils.go b/csv/parsers/utils.go index a9b9364..b928258 100644 --- a/csv/parsers/utils.go +++ b/csv/parsers/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/DefiantLabs/cosmos-tax-cli/db" "github.com/preichenberger/go-coinbasepro/v2" ) @@ -22,3 +23,31 @@ func GetRate(cbClient *coinbasepro.Client, coin string, transactionTime time.Tim return histRate[0].Close, nil } + +func AddTxToGroupMap(groupedTxs map[uint][]db.TaxableTransaction, tx db.TaxableTransaction) map[uint][]db.TaxableTransaction { + // Add tx to group using the TX ID as key and appending to array + if _, ok := groupedTxs[tx.Message.Tx.ID]; ok { + groupedTxs[tx.Message.Tx.ID] = append(groupedTxs[tx.Message.Tx.ID], tx) + } else { + var txGrouping []db.TaxableTransaction + txGrouping = append(txGrouping, tx) + groupedTxs[tx.Message.Tx.ID] = txGrouping + } + return groupedTxs +} + +func GetTxToFeesMap(groupedTxes map[uint][]db.TaxableTransaction) map[uint][]db.Fee { + txToFees := make(map[uint][]db.Fee) + + for _, txMessages := range groupedTxes { + for _, message := range txMessages { + messageTx := message.Message.Tx + if _, ok := txToFees[messageTx.ID]; !ok { + txToFees[messageTx.ID] = append(txToFees[messageTx.ID], messageTx.Fees...) + break + } + } + } + + return txToFees +} diff --git a/db/search.go b/db/search.go index 352b4bd..c313ba9 100644 --- a/db/search.go +++ b/db/search.go @@ -14,6 +14,7 @@ func GetTaxableTransactions(address string, db *gorm.DB) ([]TaxableTransaction, Preload("Message.Tx.Block"). Preload("Message.Tx.SignerAddress").Preload("Message.Tx.Fees"). Preload("Message.Tx.Fees.Denomination").Preload("Message.Tx.Fees.PayerAddress"). + Preload("Message.Tx.Fees.Tx").Preload("Message.Tx.Fees.Tx.Block"). Preload("SenderAddress").Preload("ReceiverAddress").Preload("DenominationSent"). Preload("DenominationReceived").Find(&taxableTransactions)