From aa1bb43c77bd527c3bbd033c6191776f74e1691e Mon Sep 17 00:00:00 2001 From: pharr117 Date: Sun, 28 Jan 2024 13:52:24 -0500 Subject: [PATCH] Finish implementation of all parsing groups for cl position handling messages --- csv/parsers/accointing/osmosis.go | 16 +--- csv/parsers/cointracker/osmosis.go | 41 ++------ .../cryptotaxcalculator.go | 4 +- csv/parsers/cryptotaxcalculator/osmosis.go | 93 +++++++++++++++++-- csv/parsers/cryptotaxcalculator/rows.go | 60 +++++++++++- csv/parsers/koinly/koinly.go | 2 +- csv/parsers/koinly/osmosis.go | 93 +++++++++++++++++-- csv/parsers/koinly/rows.go | 58 ++++++++++++ csv/parsers/taxbit/osmosis.go | 93 +++++++++++++++++-- csv/parsers/taxbit/rows.go | 58 ++++++++++++ csv/parsers/taxbit/taxbit.go | 2 +- csv/parsers/utils.go | 29 ++++++ 12 files changed, 472 insertions(+), 77 deletions(-) diff --git a/csv/parsers/accointing/osmosis.go b/csv/parsers/accointing/osmosis.go index 2e4ec21c..c0ab500a 100644 --- a/csv/parsers/accointing/osmosis.go +++ b/csv/parsers/accointing/osmosis.go @@ -12,18 +12,6 @@ import ( "github.com/preichenberger/go-coinbasepro/v2" ) -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 -} - type OsmosisLpTxGroup struct { GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages Rows []parsers.CsvRow @@ -51,7 +39,7 @@ func (sf *OsmosisLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { if sf.GroupedTxes == nil { sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) } - sf.GroupedTxes = addTxToGroupMap(sf.GroupedTxes, tx) + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) } func (sf *OsmosisLpTxGroup) ParseGroup() error { @@ -128,7 +116,7 @@ func (sf *OsmosisConcentratedLiquidityTxGroup) AddTxToGroup(tx db.TaxableTransac if sf.GroupedTxes == nil { sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) } - sf.GroupedTxes = addTxToGroupMap(sf.GroupedTxes, tx) + 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. diff --git a/csv/parsers/cointracker/osmosis.go b/csv/parsers/cointracker/osmosis.go index 479ba16d..da520a37 100644 --- a/csv/parsers/cointracker/osmosis.go +++ b/csv/parsers/cointracker/osmosis.go @@ -7,18 +7,6 @@ import ( "github.com/DefiantLabs/cosmos-tax-cli/util" ) -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 -} - type OsmosisLpTxGroup struct { GroupedTxes map[uint][]db.TaxableTransaction // TX db ID to its messages Rows []parsers.CsvRow @@ -45,7 +33,7 @@ func (sf *OsmosisLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { if sf.GroupedTxes == nil { sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) } - sf.GroupedTxes = addTxToGroupMap(sf.GroupedTxes, tx) + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) } func (sf *OsmosisLpTxGroup) ParseGroup() error { @@ -144,13 +132,12 @@ func (sf *OsmosisConcentratedLiquidityTxGroup) AddTxToGroup(tx db.TaxableTransac if sf.GroupedTxes == nil { sf.GroupedTxes = make(map[uint][]db.TaxableTransaction) } - sf.GroupedTxes = addTxToGroupMap(sf.GroupedTxes, tx) + 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 := getTxToFeesMap(sf.GroupedTxes) + txsToFees := parsers.GetTxToFeesMap(sf.GroupedTxes) for _, txMessages := range sf.GroupedTxes { for _, message := range txMessages { @@ -187,26 +174,12 @@ func (sf *OsmosisConcentratedLiquidityTxGroup) ParseGroup() error { for _, fees := range txsToFees { for _, fee := range fees { row := Row{} - row.ParseFee(fee.Tx, fee) + err := row.ParseFee(fee.Tx, fee) + if err != nil { + return err + } sf.Rows = append(sf.Rows, row) } } return nil } - -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/csv/parsers/cryptotaxcalculator/cryptotaxcalculator.go b/csv/parsers/cryptotaxcalculator/cryptotaxcalculator.go index 1aac0d9f..c1d3beaa 100644 --- a/csv/parsers/cryptotaxcalculator/cryptotaxcalculator.go +++ b/csv/parsers/cryptotaxcalculator/cryptotaxcalculator.go @@ -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, &OsmosisLpTxGroup{}) + 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 4b40ed8c..b2ab8572 100644 --- a/csv/parsers/cryptotaxcalculator/osmosis.go +++ b/csv/parsers/cryptotaxcalculator/osmosis.go @@ -3,6 +3,7 @@ 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" ) @@ -33,14 +34,7 @@ func (sf *OsmosisLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { if sf.GroupedTxes == nil { sf.GroupedTxes = make(map[uint][]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 - } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) } func (sf *OsmosisLpTxGroup) ParseGroup() error { @@ -85,3 +79,86 @@ func (sf *OsmosisLpTxGroup) ParseGroup() 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 4b4705c7..5cc0de1a 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 3bd2cd6e..005fc259 100644 --- a/csv/parsers/koinly/koinly.go +++ b/csv/parsers/koinly/koinly.go @@ -107,7 +107,7 @@ func (p *Parser) ProcessTaxableEvent(taxableEvents []db.TaxableEvent) error { } func (p *Parser) InitializeParsingGroups() { - p.ParsingGroups = append(p.ParsingGroups, &OsmosisLpTxGroup{}) + 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 93558ac0..c250edb5 100644 --- a/csv/parsers/koinly/osmosis.go +++ b/csv/parsers/koinly/osmosis.go @@ -7,6 +7,7 @@ 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" @@ -38,14 +39,7 @@ func (sf *OsmosisLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { if sf.GroupedTxes == nil { sf.GroupedTxes = make(map[uint][]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 - } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) } func (sf *OsmosisLpTxGroup) ParseGroup() error { @@ -114,3 +108,86 @@ func (sf *OsmosisLpTxGroup) ParseGroup() 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 c4c04a53..218ae30d 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/taxbit/osmosis.go b/csv/parsers/taxbit/osmosis.go index 97f09e81..9fb5f69c 100644 --- a/csv/parsers/taxbit/osmosis.go +++ b/csv/parsers/taxbit/osmosis.go @@ -3,6 +3,7 @@ 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" ) @@ -32,14 +33,7 @@ func (sf *OsmosisLpTxGroup) AddTxToGroup(tx db.TaxableTransaction) { if sf.GroupedTxes == nil { sf.GroupedTxes = make(map[uint][]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 - } + sf.GroupedTxes = parsers.AddTxToGroupMap(sf.GroupedTxes, tx) } func (sf *OsmosisLpTxGroup) ParseGroup() error { @@ -110,3 +104,86 @@ func (sf *OsmosisLpTxGroup) ParseGroup() 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 72c5d7c7..def70905 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 3567c5af..0ad26e16 100644 --- a/csv/parsers/taxbit/taxbit.go +++ b/csv/parsers/taxbit/taxbit.go @@ -79,7 +79,7 @@ func (p *Parser) ProcessTaxableEvent(taxableEvents []db.TaxableEvent) error { } func (p *Parser) InitializeParsingGroups() { - p.ParsingGroups = append(p.ParsingGroups, &OsmosisLpTxGroup{}) + 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/utils.go b/csv/parsers/utils.go index a9b93649..b9282583 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 +}