Repository: rotki/rotki

Issue:
crypto.com importer: missing 'Transaction Kind's
## Problem Definition

Some things I noticed with the crypto.com importer: rotkehlchen/data/importer.py

Two **'Transaction Kind's** seem to be **missing**:
* **supercharger_reward_to_app_credited**: Example line 
Timestamp (UTC) | Transaction Description | Currency | Amount | To Currency | To Amount | Native Currency | Native Amount | Native Amount (in USD) | Transaction Kind
2021-10-28 03:16:37 | Supercharger Reward | AXS | 0.00356292 |   |   | EUR | 0.38 | 0.4430698990794 | supercharger_reward_to_app_credited
* **recurring_buy_order**: probably needs to be handled as Trade: https://github.com/rotki/rotki/blob/develop/rotkehlchen/data/importer.py#L240

Not an issue but maybe worth to mention it: I don't think there is a 'Transaction Kind' reinbursment https://github.com/rotki/rotki/blob/develop/rotkehlchen/data/importer.py#L326. reimbursement does exist though and already is handled as Trade in https://github.com/rotki/rotki/blob/develop/rotkehlchen/data/importer.py#L248

Rotki version: 1.23.0



Discussion:
Hello! Thanks for reporting them. Could you please provide an example for the `recurring_buy_order`?
It's pretty much like viban_purchase:

Timestamp (UTC) | Transaction Description | Currency | Amount | To Currency | To Amount | Native Currency | Native Amount | Native Amount (in USD) | Transaction Kind
2021-10-13 21:00:09 | Recurring Buy | EUR | 80 | ETH | 0.0246158182949 | EUR | 80 | 93.2778734904 | recurring_buy_order

Sorry, it actually differs from viban_purchase: for `recurring_buy_order` the amount value is positive.

Resolving patch:
diff --git a/rotkehlchen/assets/asset.py b/rotkehlchen/assets/asset.py
--- a/rotkehlchen/assets/asset.py
+++ b/rotkehlchen/assets/asset.py
@@ -545,6 +545,10 @@ def deserialize_from_db(cls, entry: UnderlyingTokenDBTuple) -> 'UnderlyingToken'
     'LUNA-2': 'LUNA',  # Luna Terra
 }
 
+WORLD_TO_CRYPTOCOM = {
+    strethaddress_to_identifier('0xBB0E17EF65F82Ab018d8EDd776e8DD940327B28b'): 'AXS',
+}
+
 # Create a generic variable that can be 'Asset', or any subclass.
 Z = TypeVar('Z', bound='Asset')
 
diff --git a/rotkehlchen/assets/converters.py b/rotkehlchen/assets/converters.py
--- a/rotkehlchen/assets/converters.py
+++ b/rotkehlchen/assets/converters.py
@@ -8,6 +8,7 @@
     WORLD_TO_BITTREX,
     WORLD_TO_COINBASE,
     WORLD_TO_COINBASE_PRO,
+    WORLD_TO_CRYPTOCOM,
     WORLD_TO_FTX,
     WORLD_TO_GEMINI,
     WORLD_TO_ICONOMI,
@@ -750,6 +751,7 @@
 GEMINI_TO_WORLD = {v: k for k, v in WORLD_TO_GEMINI.items()}
 NEXO_TO_WORLD = {v: k for k, v in WORLD_TO_NEXO.items()}
 BITPANDA_TO_WORLD = {v: k for k, v in WORLD_TO_BITPANDA.items()}
+CRYPTOCOM_TO_WORLD = {v: k for k, v in WORLD_TO_CRYPTOCOM.items()}
 
 RENAMED_BINANCE_ASSETS = {
     # The old BCC in binance forked into BCHABC and BCHSV
@@ -1031,3 +1033,18 @@ def asset_from_bitpanda(bitpanda_name: str) -> Asset:
 
     our_name = BITPANDA_TO_WORLD.get(bitpanda_name, bitpanda_name)
     return symbol_to_asset_or_token(our_name)
+
+
+def asset_from_cryptocom(cryptocom_name: str) -> Asset:
+    """May raise:
+    - DeserializationError
+    - UnsupportedAsset
+    - UnknownAsset
+    """
+    if not isinstance(cryptocom_name, str):
+        raise DeserializationError(
+            f'Got non-string type {type(cryptocom_name)} for cryptocom asset',
+        )
+
+    symbol = CRYPTOCOM_TO_WORLD.get(cryptocom_name, cryptocom_name)
+    return symbol_to_asset_or_token(symbol)
diff --git a/rotkehlchen/data/importer.py b/rotkehlchen/data/importer.py
--- a/rotkehlchen/data/importer.py
+++ b/rotkehlchen/data/importer.py
@@ -9,7 +9,7 @@
 from pysqlcipher3 import dbapi2 as sqlcipher
 
 from rotkehlchen.accounting.ledger_actions import LedgerAction, LedgerActionType
-from rotkehlchen.assets.converters import asset_from_nexo, asset_from_uphold
+from rotkehlchen.assets.converters import asset_from_cryptocom, asset_from_nexo, asset_from_uphold
 from rotkehlchen.assets.utils import symbol_to_asset_or_token
 from rotkehlchen.constants.assets import A_BSQ, A_BTC, A_DAI, A_SAI, A_USD
 from rotkehlchen.constants.misc import ZERO
@@ -117,11 +117,11 @@ def _consume_cointracking_entry(self, csv_row: Dict[str, Any], **kwargs: Any) ->
         fee_currency = A_USD  # whatever (used only if there is no fee)
         if csv_row['Fee'] != '':
             fee = deserialize_fee(csv_row['Fee'])
-            fee_currency = symbol_to_asset_or_token(csv_row['Cur.Fee'])
+            fee_currency = asset_from_cryptocom(csv_row['Cur.Fee'])
 
         if row_type in ('Gift/Tip', 'Trade', 'Income'):
-            base_asset = symbol_to_asset_or_token(csv_row['Cur.Buy'])
-            quote_asset = None if csv_row['Cur.Sell'] == '' else symbol_to_asset_or_token(csv_row['Cur.Sell'])  # noqa: E501
+            base_asset = asset_from_cryptocom(csv_row['Cur.Buy'])
+            quote_asset = None if csv_row['Cur.Sell'] == '' else asset_from_cryptocom(csv_row['Cur.Sell'])  # noqa: E501
             if quote_asset is None and row_type not in ('Gift/Tip', 'Income'):
                 raise DeserializationError('Got a trade entry with an empty quote asset')
 
@@ -153,10 +153,10 @@ def _consume_cointracking_entry(self, csv_row: Dict[str, Any], **kwargs: Any) ->
             category = deserialize_asset_movement_category(row_type.lower())
             if category == AssetMovementCategory.DEPOSIT:
                 amount = deserialize_asset_amount(csv_row['Buy'])
-                asset = symbol_to_asset_or_token(csv_row['Cur.Buy'])
+                asset = asset_from_cryptocom(csv_row['Cur.Buy'])
             else:
                 amount = deserialize_asset_amount_force_positive(csv_row['Sell'])
-                asset = symbol_to_asset_or_token(csv_row['Cur.Sell'])
+                asset = asset_from_cryptocom(csv_row['Cur.Sell'])
 
             asset_movement = AssetMovement(
                 location=location,
@@ -248,6 +248,7 @@ def _consume_cryptocom_entry(self, csv_row: Dict[str, Any], **kwargs: Any) -> No
             'reimbursement',
             'viban_purchase',
             'crypto_viban_exchange',
+            'recurring_buy_order',
         ):
             # variable mapping to raw data
             currency = csv_row['Currency']
@@ -259,17 +260,17 @@ def _consume_cryptocom_entry(self, csv_row: Dict[str, Any], **kwargs: Any) -> No
 
             trade_type = TradeType.BUY if to_currency != native_currency else TradeType.SELL
 
-            if row_type in ('crypto_exchange', 'crypto_viban_exchange'):
+            if row_type in ('crypto_exchange', 'crypto_viban_exchange', 'recurring_buy_order'):
                 # trades crypto to crypto
-                base_asset = symbol_to_asset_or_token(to_currency)
-                quote_asset = symbol_to_asset_or_token(currency)
+                base_asset = asset_from_cryptocom(to_currency)
+                quote_asset = asset_from_cryptocom(currency)
                 if quote_asset is None:
                     raise DeserializationError('Got a trade entry with an empty quote asset')
                 base_amount_bought = deserialize_asset_amount(to_amount)
                 quote_amount_sold = deserialize_asset_amount(amount)
             else:
-                base_asset = symbol_to_asset_or_token(currency)
-                quote_asset = symbol_to_asset_or_token(native_currency)
+                base_asset = asset_from_cryptocom(currency)
+                quote_asset = asset_from_cryptocom(native_currency)
                 base_amount_bought = deserialize_asset_amount(amount)
                 quote_amount_sold = deserialize_asset_amount(native_amount)
 
@@ -302,7 +303,7 @@ def _consume_cryptocom_entry(self, csv_row: Dict[str, Any], **kwargs: Any) -> No
                 category = AssetMovementCategory.DEPOSIT
                 amount = deserialize_asset_amount(csv_row['Amount'])
 
-            asset = symbol_to_asset_or_token(csv_row['Currency'])
+            asset = asset_from_cryptocom(csv_row['Currency'])
             asset_movement = AssetMovement(
                 location=Location.CRYPTOCOM,
                 category=category,
@@ -323,11 +324,11 @@ def _consume_cryptocom_entry(self, csv_row: Dict[str, Any], **kwargs: Any) -> No
             'pay_checkout_reward'
             'transfer_cashback',
             'rewards_platform_deposit_credited',
-            'reinbursment',
             'pay_checkout_reward',
             'transfer_cashback',
+            'supercharger_reward_to_app_credited',
         ):
-            asset = symbol_to_asset_or_token(csv_row['Currency'])
+            asset = asset_from_cryptocom(csv_row['Currency'])
             amount = deserialize_asset_amount(csv_row['Amount'])
             action = LedgerAction(
                 identifier=0,  # whatever is not used at insertion
@@ -343,7 +344,7 @@ def _consume_cryptocom_entry(self, csv_row: Dict[str, Any], **kwargs: Any) -> No
             )
             self.db_ledger.add_ledger_action(action)
         elif row_type in ('crypto_payment', 'reimbursement_reverted'):
-            asset = symbol_to_asset_or_token(csv_row['Currency'])
+            asset = asset_from_cryptocom(csv_row['Currency'])
             amount = abs(deserialize_asset_amount(csv_row['Amount']))
             action = LedgerAction(
                 identifier=0,  # whatever is not used at insertion
@@ -359,7 +360,7 @@ def _consume_cryptocom_entry(self, csv_row: Dict[str, Any], **kwargs: Any) -> No
             )
             self.db_ledger.add_ledger_action(action)
         elif row_type == 'invest_deposit':
-            asset = symbol_to_asset_or_token(csv_row['Currency'])
+            asset = asset_from_cryptocom(csv_row['Currency'])
             amount = deserialize_asset_amount(csv_row['Amount'])
             asset_movement = AssetMovement(
                 location=Location.CRYPTOCOM,
@@ -375,7 +376,7 @@ def _consume_cryptocom_entry(self, csv_row: Dict[str, Any], **kwargs: Any) -> No
             )
             self.db.add_asset_movements([asset_movement])
         elif row_type == 'invest_withdrawal':
-            asset = symbol_to_asset_or_token(csv_row['Currency'])
+            asset = asset_from_cryptocom(csv_row['Currency'])
             amount = deserialize_asset_amount(csv_row['Amount'])
             asset_movement = AssetMovement(
                 location=Location.CRYPTOCOM,
@@ -391,7 +392,7 @@ def _consume_cryptocom_entry(self, csv_row: Dict[str, Any], **kwargs: Any) -> No
             )
             self.db.add_asset_movements([asset_movement])
         elif row_type == 'crypto_transfer':
-            asset = symbol_to_asset_or_token(csv_row['Currency'])
+            asset = asset_from_cryptocom(csv_row['Currency'])
             amount = deserialize_asset_amount(csv_row['Amount'])
             if amount < 0:
                 action_type = LedgerActionType.EXPENSE


Test patch:
diff --git a/rotkehlchen/tests/api/test_nfts.py b/rotkehlchen/tests/api/test_nfts.py
--- a/rotkehlchen/tests/api/test_nfts.py
+++ b/rotkehlchen/tests/api/test_nfts.py
@@ -55,7 +55,7 @@ def test_nft_query(rotkehlchen_api_server, start_with_valid_premium):
 
             assert entry['name'] == 'MoonCat #129: 0x0082206dcb'
             assert entry['permalink'] == 'https://opensea.io/assets/0xc3f733ca98e0dad0386979eb96fb1722a1a05e69/129'  # noqa: E501
-            assert entry['external_link'] is None
+            assert entry['external_link'] == 'https://purrse.mooncat.community/129'
             assert entry['image_url'] == 'https://lh3.googleusercontent.com/C5ceArerHdGigmSt9tCsUD67Nbxr-P05fSsP_Pye34zN78lr2519P66kANkb55nfWW-ZMWFM4oCawLF4fW2jfRPxWButdqIX0QM95DM'  # noqa: E501
             assert FVal(entry['price_eth']) > ZERO
             assert FVal(entry['price_usd']) > ZERO
diff --git a/rotkehlchen/tests/data/cryptocom_special_events.csv b/rotkehlchen/tests/data/cryptocom_special_events.csv
--- a/rotkehlchen/tests/data/cryptocom_special_events.csv
+++ b/rotkehlchen/tests/data/cryptocom_special_events.csv
@@ -11,4 +11,5 @@ Timestamp (UTC),Transaction Description,Currency,Amount,To Currency,To Amount,Na
 2021-01-01 23:00:00,Invest Deposit,BTC,-0.01,,,USD,-100,-100,invest_deposit
 2021-01-01 22:00:00,Invest Deposit,BTC,-0.01,,,USD,-100,-100,invest_deposit
 2021-01-01 21:01:00,Invest Withdrawal,ETC,0.24,,,USD,100,100,invest_withdrawal
-2021-01-01 21:00:00,Invest Deposit,ETC,-1.0,,,USD,-100,-100,invest_deposit
\ No newline at end of file
+2021-01-01 21:00:00,Invest Deposit,ETC,-1.0,,,USD,-100,-100,invest_deposit
+2021-10-28 03:16:37,Supercharger Reward,AXS,0.00356292,,,EUR,0.38,0.4430698990794,supercharger_reward_to_app_credited
\ No newline at end of file
diff --git a/rotkehlchen/tests/utils/dataimport.py b/rotkehlchen/tests/utils/dataimport.py
--- a/rotkehlchen/tests/utils/dataimport.py
+++ b/rotkehlchen/tests/utils/dataimport.py
@@ -1,4 +1,5 @@
 from rotkehlchen.accounting.ledger_actions import LedgerAction, LedgerActionType
+from rotkehlchen.assets.converters import asset_from_cryptocom
 from rotkehlchen.assets.utils import symbol_to_asset_or_token
 from rotkehlchen.constants.assets import (
     A_BAT,
@@ -438,6 +439,17 @@ def assert_cryptocom_special_events_import_results(rotki: Rotkehlchen):
     assert len(warnings) == 0
 
     expected_actions = [LedgerAction(
+        identifier=6,
+        timestamp=Timestamp(1635390997),
+        action_type=LedgerActionType.INCOME,
+        location=Location.CRYPTOCOM,
+        amount=AssetAmount(FVal('0.00356292')),
+        asset=asset_from_cryptocom('AXS'),
+        rate=None,
+        rate_asset=None,
+        link=None,
+        notes=get_cryptocom_note('Supercharger Reward'),
+    ), LedgerAction(
         identifier=5,
         timestamp=Timestamp(1609884000),
         action_type=LedgerActionType.INCOME,