diff --git a/csv/parsers/accointing/osmosis.go b/csv/parsers/accointing/osmosis.go index 2e4ec21..c0ab500 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 479ba16..da520a3 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 1aac0d9..c1d3bea 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 4b40ed8..b2ab857 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 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 3bd2cd6..005fc25 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 93558ac..c250edb 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 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/taxbit/osmosis.go b/csv/parsers/taxbit/osmosis.go index 97f09e8..9fb5f69 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 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 3567c5a..0ad26e1 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 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 +}