Skip to content

Commit

Permalink
Merge pull request #2783 from yabirgb/issue-1674
Browse files Browse the repository at this point in the history
Add blockfi and nexo
  • Loading branch information
LefterisJP committed May 10, 2021
2 parents 97b0688 + be87f62 commit 88d0299
Show file tree
Hide file tree
Showing 30 changed files with 789 additions and 55 deletions.
2 changes: 2 additions & 0 deletions docs/changelog.rst
Expand Up @@ -3,6 +3,8 @@ Changelog
=========

* :feature:`1549` Rotki premium users will now be able to switch to a dark mode and change the theme colors.
* :feature:`1674` Add experimental support for BlockFi imports using CSV files.
* :feature:`2224` Add experimental support for Nexo imports using CSV files.

* :release:`1.16.2 <2021-05-08>`
* :bug:`-` If a DeFi event provides zero amount of an asset to a user the PnL report should now work properly again.
Expand Down
Binary file added frontend/app/src/assets/images/blockfi.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions frontend/app/src/assets/images/import/blockfi.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/app/src/assets/images/import/nexo.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/app/src/assets/images/nexo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions frontend/app/src/components/history/consts.ts
Expand Up @@ -8,6 +8,7 @@ import {
EXCHANGE_BITMEX,
EXCHANGE_BITSTAMP,
EXCHANGE_BITTREX,
EXCHANGE_BLOCKFI,
EXCHANGE_COINBASE,
EXCHANGE_COINBASEPRO,
EXCHANGE_CRYPTOCOM,
Expand All @@ -16,6 +17,7 @@ import {
EXCHANGE_ICONOMI,
EXCHANGE_KRAKEN,
EXCHANGE_KUCOIN,
EXCHANGE_NEXO,
EXCHANGE_POLONIEX,
EXCHANGE_UNISWAP,
TRADE_LOCATION_BANKS,
Expand Down Expand Up @@ -128,13 +130,27 @@ export const tradeLocations: TradeLocationData[] = [
imageIcon: true,
exchange: false
},
{
identifier: EXCHANGE_BLOCKFI,
name: 'BlockFi',
icon: require('@/assets/images/blockfi.png'),
imageIcon: true,
exchange: true
},
{
identifier: EXCHANGE_CRYPTOCOM,
name: 'Crypto.com',
icon: require('@/assets/images/crypto.com.png'),
imageIcon: true,
exchange: false
},
{
identifier: EXCHANGE_NEXO,
name: 'Nexo',
icon: require('@/assets/images/nexo.png'),
imageIcon: true,
exchange: true
},
{
identifier: EXCHANGE_BITSTAMP,
name: 'Bitstamp',
Expand Down
9 changes: 8 additions & 1 deletion frontend/app/src/components/import/FileUpload.vue
Expand Up @@ -59,7 +59,14 @@
<script lang="ts">
import { Component, Emit, Prop, Vue } from 'vue-property-decorator';
const SOURCES = ['cointracking.info', 'cryptocom', 'icon'];
const SOURCES = [
'cointracking.info',
'cryptocom',
'icon',
'nexo',
'blockfi-transactions',
'blockfi-trades'
];
@Component({})
export default class FileUpload extends Vue {
Expand Down
Expand Up @@ -43,7 +43,7 @@ import PurgeSelector, {
PurgeParams
} from '@/components/settings/data-security/PurgeSelector.vue';
import StatusButton from '@/components/settings/data-security/StatusButton.vue';
import { EXCHANGE_CRYPTOCOM, SUPPORTED_EXCHANGES } from '@/data/defaults';
import { EXTERNAL_EXCHANGES, SUPPORTED_EXCHANGES } from '@/data/defaults';
import { SupportedExchange } from '@/services/balances/types';
import {
ALL_DECENTRALIZED_EXCHANGES,
Expand Down Expand Up @@ -117,7 +117,7 @@ export default class DataManagement extends Vue {
} else {
if (
SUPPORTED_EXCHANGES.includes(source as any) ||
source === EXCHANGE_CRYPTOCOM
EXTERNAL_EXCHANGES.includes(source as any)
) {
await this.$api.balances.deleteExchangeData(
source as SupportedExchange
Expand Down
10 changes: 10 additions & 0 deletions frontend/app/src/data/defaults.ts
Expand Up @@ -33,6 +33,8 @@ export const EXCHANGE_BITCOIN_DE = 'bitcoinde';
export const EXCHANGE_ICONOMI = 'iconomi';
export const EXCHANGE_KUCOIN = 'kucoin';
export const EXCHANGE_FTX = 'ftx';
export const EXCHANGE_BLOCKFI = 'blockfi';
export const EXCHANGE_NEXO = 'nexo';

export const EXCHANGE_UNISWAP = 'uniswap';
export const EXCHANGE_BALANCER = 'balancer';
Expand Down Expand Up @@ -69,6 +71,8 @@ export const SUPPORTED_TRADE_LOCATIONS = [
EXCHANGE_UNISWAP,
EXCHANGE_BALANCER,
EXCHANGE_CRYPTOCOM,
EXCHANGE_BLOCKFI,
EXCHANGE_NEXO,
TRADE_LOCATION_EXTERNAL,
TRADE_LOCATION_BANKS,
TRADE_LOCATION_EQUITIES,
Expand All @@ -81,3 +85,9 @@ export const ALL_TRADE_LOCATIONS = [
...SUPPORTED_TRADE_LOCATIONS,
...SUPPORTED_EXCHANGES
] as const;

export const EXTERNAL_EXCHANGES = [
EXCHANGE_CRYPTOCOM,
EXCHANGE_BLOCKFI,
EXCHANGE_NEXO
];
16 changes: 16 additions & 0 deletions frontend/app/src/locales/en.json
Expand Up @@ -2258,6 +2258,22 @@
"name": "crypto.com mobile app",
"note": "Important notes for importing data from {0}'s CSV exports."
},
"blockfi": {
"import": "Import {0} from BlockFi",
"import_trade": "trades",
"import_transactions": "transactions",
"name": "BlockFi",
"line_one": "[Experimental] If you find an issue with the imported data, let us know so we can adapt the code accordingly.",
"line_two": "BlockFi lets you export two different files. Trades will be imported from the file that only contains trades.",
"note": "Important notes for importing data from {0}'s CSV exports."
},
"nexo": {
"import": "Import {0} from Nexo",
"import_transactions": "transactions",
"name": "Nexo",
"line_one": "[Experimental] If you find an issue with the imported data let us know so we can adapt the code accordingly.",
"note": "Important notes for importing data from {0}'s CSV exports."
},
"description": "You can manually import data from the services below by dragging the file on the respective area or clicking the select button.",
"title": "Import Data",
"notice": "{warning} Those platforms could change their format in a backwards incompatible way quite often. As such this import may stop working. If that happens {link} and we will see what we can do.",
Expand Down
4 changes: 3 additions & 1 deletion frontend/app/src/services/balances/types.d.ts
@@ -1,13 +1,15 @@
import { default as BigNumber } from 'bignumber.js';
import {
SUPPORTED_EXCHANGES,
SUPPORTED_TRADE_LOCATIONS
SUPPORTED_TRADE_LOCATIONS,
EXTERNAL_EXCHANGES
} from '@/data/defaults';
import { TradeLocation } from '@/services/history/types';
import { Balance } from '@/services/types-api';

export type SupportedExchange = typeof SUPPORTED_EXCHANGES[number];
export type SupportedTradeLocation = typeof SUPPORTED_TRADE_LOCATIONS[number];
export type SupportedExternalExchanges = typeof EXTERNAL_EXCHANGES[number];

export interface ManualBalance {
readonly asset: string;
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/src/services/session/consts.ts
@@ -1,4 +1,4 @@
import { EXCHANGE_CRYPTOCOM, SUPPORTED_EXCHANGES } from '@/data/defaults';
import { EXTERNAL_EXCHANGES, SUPPORTED_EXCHANGES } from '@/data/defaults';

export const MODULE_YEARN = 'yearn_vaults';
export const MODULE_COMPOUND = 'compound';
Expand Down Expand Up @@ -34,6 +34,6 @@ export const PURGABLE = [
ALL_MODULES,
ALL_TRANSACTIONS,
...SUPPORTED_EXCHANGES,
EXCHANGE_CRYPTOCOM,
...EXTERNAL_EXCHANGES,
...MODULES
];
6 changes: 3 additions & 3 deletions frontend/app/src/store/history/actions.ts
@@ -1,6 +1,6 @@
import { ActionTree } from 'vuex';
import { exchangeName } from '@/components/history/consts';
import { EXCHANGE_CRYPTOCOM, TRADE_LOCATION_EXTERNAL } from '@/data/defaults';
import { TRADE_LOCATION_EXTERNAL, EXTERNAL_EXCHANGES } from '@/data/defaults';
import i18n from '@/i18n';
import { createTask, taskCompletion, TaskMeta } from '@/model/task';
import { TaskType } from '@/model/task-type';
Expand Down Expand Up @@ -116,7 +116,7 @@ export const actions: ActionTree<HistoryState, RotkehlchenState> = {
const { connectedExchanges } = balances!;
const locations: TradeLocation[] = [
...(source !== FETCH_FROM_SOURCE
? ([TRADE_LOCATION_EXTERNAL, EXCHANGE_CRYPTOCOM] as TradeLocation[])
? ([TRADE_LOCATION_EXTERNAL, ...EXTERNAL_EXCHANGES] as TradeLocation[])
: []),
...connectedExchanges
];
Expand Down Expand Up @@ -346,7 +346,7 @@ export const actions: ActionTree<HistoryState, RotkehlchenState> = {
await Promise.all(
([
...locations,
...(source !== FETCH_FROM_SOURCE ? [EXCHANGE_CRYPTOCOM] : [])
...(source !== FETCH_FROM_SOURCE ? EXTERNAL_EXCHANGES : [])
] as TradeLocation[]).map(location =>
fetchLocation(location).catch(e => onError(location, e.message))
)
Expand Down
9 changes: 6 additions & 3 deletions frontend/app/src/store/session/actions.ts
Expand Up @@ -3,12 +3,15 @@ import {
convertToAccountingSettings,
convertToGeneralSettings
} from '@/data/converters';
import { EXCHANGE_CRYPTOCOM, SUPPORTED_EXCHANGES } from '@/data/defaults';
import { EXTERNAL_EXCHANGES, SUPPORTED_EXCHANGES } from '@/data/defaults';
import i18n from '@/i18n';
import { DBSettings } from '@/model/action-result';
import { createTask, taskCompletion, TaskMeta } from '@/model/task';
import { TaskType } from '@/model/task-type';
import { SupportedExchange } from '@/services/balances/types';
import {
SupportedExchange,
SupportedExternalExchanges
} from '@/services/balances/types';
import { balanceKeys } from '@/services/consts';
import { monitor } from '@/services/monitoring';
import { api } from '@/services/rotkehlchen-api';
Expand Down Expand Up @@ -545,7 +548,7 @@ export const actions: ActionTree<SessionState, RotkehlchenState> = {
await dispatch(`defi/${ACTION_PURGE_PROTOCOL}`, ALL_MODULES, opts);
} else if (
SUPPORTED_EXCHANGES.includes(purgable as SupportedExchange) ||
purgable === EXCHANGE_CRYPTOCOM
EXTERNAL_EXCHANGES.includes(purgable as SupportedExternalExchanges)
) {
await dispatch(`history/${ACTION_PURGE_EXCHANGE}`, purgable, opts);
} else if (MODULES.includes(purgable as SupportedModules)) {
Expand Down
69 changes: 68 additions & 1 deletion frontend/app/src/views/ImportData.vue
Expand Up @@ -72,7 +72,7 @@
</v-row>
<v-row>
<v-col>
<file-upload source="crypto.com" />
<file-upload source="cryptocom" />
</v-col>
</v-row>
<v-row>
Expand Down Expand Up @@ -109,6 +109,73 @@
</ul>
</v-col>
</v-row>
<v-divider class="mt-1" />
<v-row>
<v-col cols="12">
<div class="import-data__crypto-com">
<v-img
max-width="200"
:src="require('@/assets/images/import/blockfi.svg')"
/>
</div>
</v-col>
</v-row>
<v-row>
<v-col>
<i18n tag="span" path="import_data.blockfi.import">
<strong v-text="$t('import_data.blockfi.import_trade')" />
</i18n>
<file-upload source="blockfi-trades" />
</v-col>
<v-col>
<i18n tag="span" path="import_data.blockfi.import">
<strong
v-text="$t('import_data.blockfi.import_transactions')"
/>
</i18n>
<file-upload source="blockfi-transactions" />
</v-col>
</v-row>
<v-row>
<v-col>
<i18n tag="span" path="import_data.blockfi.note">
<strong v-text="$t('import_data.blockfi.name')" />
</i18n>
<ul>
<li>{{ $t('import_data.blockfi.line_one') }}</li>
<li>{{ $t('import_data.blockfi.line_two') }}</li>
</ul>
</v-col>
</v-row>
<v-divider class="mt-1" />
<v-row>
<v-col cols="12">
<div class="import-data__crypto-com">
<v-img
max-width="200"
:src="require('@/assets/images/import/nexo.svg')"
/>
</div>
</v-col>
</v-row>
<v-row>
<v-col>
<i18n tag="span" path="import_data.nexo.import">
<strong v-text="$t('import_data.nexo.import_transactions')" />
</i18n>
<file-upload source="nexo" />
</v-col>
</v-row>
<v-row>
<v-col>
<i18n tag="span" path="import_data.nexo.note">
<strong v-text="$t('import_data.nexo.name')" />
</i18n>
<ul>
<li>{{ $t('import_data.nexo.line_one') }}</li>
</ul>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
Expand Down
19 changes: 18 additions & 1 deletion rotkehlchen/api/rest.py
Expand Up @@ -93,6 +93,7 @@
ExternalServiceApiCredentials,
Fee,
HexColorCode,
IMPORTABLE_LOCATIONS,
ListOfBlockchainAddresses,
Location,
ModuleName,
Expand Down Expand Up @@ -1989,7 +1990,7 @@ def ping() -> Response:
@require_loggedin_user()
def import_data(
self,
source: Literal['cointracking.info', 'cryptocom'],
source: IMPORTABLE_LOCATIONS,
filepath: Path,
) -> Response:
if source == 'cointracking.info':
Expand All @@ -2002,6 +2003,22 @@ def import_data(
if not success:
result = wrap_in_fail_result(f'Invalid CSV format, missing required field: {msg}')
return api_response(result, status_code=HTTPStatus.BAD_REQUEST)
elif source == 'blockfi-transactions':
success, msg = self.rotkehlchen.data_importer.import_blockfi_transactions_csv(filepath)
if not success:
result = wrap_in_fail_result(f'Invalid CSV format, missing required field: {msg}')
return api_response(result, status_code=HTTPStatus.BAD_REQUEST)
elif source == 'blockfi-trades':
success, msg = self.rotkehlchen.data_importer.import_blockfi_trades_csv(filepath)
if not success:
result = wrap_in_fail_result(f'Invalid CSV format, missing required field: {msg}')
return api_response(result, status_code=HTTPStatus.BAD_REQUEST)
elif source == 'nexo':
success, msg = self.rotkehlchen.data_importer.import_nexo_csv(filepath)
if not success:
result = wrap_in_fail_result(f'Invalid CSV format, missing required field: {msg}')
return api_response(result, status_code=HTTPStatus.BAD_REQUEST)

return api_response(OK_RESULT, status_code=HTTPStatus.OK)

def _get_eth2_stake_deposits(self) -> Dict[str, Any]:
Expand Down
10 changes: 9 additions & 1 deletion rotkehlchen/api/v1/encoding.py
Expand Up @@ -1631,7 +1631,15 @@ class QueriedAddressesSchema(Schema):
class DataImportSchema(Schema):
source = fields.String(
required=True,
validate=webargs.validate.OneOf(choices=('cointracking.info', 'cryptocom')),
validate=webargs.validate.OneOf(
choices=(
'cointracking.info',
'cryptocom',
'blockfi-transactions',
'blockfi-trades',
'nexo',
),
),
)
file = FileField(required=True, allowed_extensions=('.csv',))

Expand Down

0 comments on commit 88d0299

Please sign in to comment.