Commit 7335000f authored by tom goriunov's avatar tom goriunov Committed by GitHub

TVL stats and Rootstock locked BTC (#1297)

* Handle new TVL property in /api/v2/stats/charts/market endpoint

Fixes #1250

* Support stats widget for Rootstock

* fix envs validation

* fix tests
parent 4b42b71e
......@@ -64,7 +64,7 @@ async function validateEnvs(appEnvs: Record<string, string>) {
async function getExternalJsonContent(envName: string): Promise<string | void> {
return new Promise((resolve, reject) => {
const fileName = `./public${ buildExternalAssetFilePath(envName, '.json') }`;
const fileName = `./public${ buildExternalAssetFilePath(envName, 'https://foo.bar/baz.json') }`;
fs.readFile(path.resolve(__dirname, fileName), 'utf8', (err, data) => {
if (err) {
......
......@@ -339,7 +339,7 @@ const schema = yup
.array()
.transform(replaceQuotes)
.json()
.of(yup.string<ChainIndicatorId>().oneOf([ 'daily_txs', 'coin_price', 'market_cap' ])),
.of(yup.string<ChainIndicatorId>().oneOf([ 'daily_txs', 'coin_price', 'market_cap', 'tvl' ])),
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR: yup.string(),
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: yup.string(),
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER: yup.boolean(),
......
......@@ -92,7 +92,7 @@ The app instance could be customized by passing following variables to NodeJS en
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'coin_price' \| 'market_cap'>` | List of charts displayed on the home page | - | - | `['daily_txs','coin_price','market_cap']` |
| NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'coin_price' \| 'market_cap' \| 'tvl'>` | List of charts displayed on the home page | - | - | `['daily_txs','coin_price','market_cap']` |
| NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR | `string` | Text color of the hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | `white` | `\#DCFE76` |
| NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND | `string` | Background css value for hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | `radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)` | `radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%)` \| `no-repeat bottom 20% right 0px/100% url(https://placekitten/1400/200)` |
| NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` | Set to false if network doesn't have gas tracker | - | `true` | `false` |
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 5.5V7h1V5.5h2V7a3 3 0 0 1 2.236 5A3 3 0 0 1 14 17v1.5h-2V17h-1v1.5H9V17H7.5v-2H9V9H7.5V7H9V5.5h2ZM11 9v2h3a1 1 0 1 0 0-2h-3Zm3 4h-3v2h3a1 1 0 1 0 0-2Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12Zm11-9a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.729 18.75h-8.1a1.35 1.35 0 0 1-1.35-1.35v-4.95a1.35 1.35 0 0 1 1.35-1.35h8.1a1.35 1.35 0 0 1 1.35 1.35v4.95a1.35 1.35 0 0 1-1.35 1.35ZM8.629 12a.45.45 0 0 0-.45.45v4.95a.45.45 0 0 0 .45.45h8.1a.45.45 0 0 0 .45-.45v-4.95a.45.45 0 0 0-.45-.45h-8.1Z" fill="currentColor" stroke="currentColor" stroke-width=".5"/>
<path d="M16.279 12h-7.2a.45.45 0 0 1-.45-.45v-2.7a3.6 3.6 0 0 1 3.6-3.6h.9a3.6 3.6 0 0 1 3.6 3.6v2.7a.45.45 0 0 1-.45.45Zm-6.75-.9h6.3V8.85a2.7 2.7 0 0 0-2.7-2.7h-.9a2.7 2.7 0 0 0-2.7 2.7v2.25Zm3.15 4.05a.9.9 0 1 1 0-1.8.9.9 0 0 1 0 1.8Z" fill="currentColor" stroke="currentColor" stroke-width=".5"/>
<path d="M13.129 14.7h-.9v1.8h.9v-1.8Z" fill="currentColor" stroke="currentColor" stroke-width=".5"/>
</svg>
......@@ -17,4 +17,10 @@ export const base: HomeStats = {
total_gas_used: '0',
total_transactions: '82258122',
transactions_today: '26815',
tvl: '1767425.102766552',
};
export const withBtcLocked: HomeStats = {
...base,
rootstock_locked_btc: '3337493406696977561374',
};
......@@ -17,6 +17,7 @@ export const HOMEPAGE_STATS: HomeStats = {
total_gas_used: '0',
total_transactions: '193823272',
transactions_today: '0',
tvl: '1767425.102766552',
};
export const STATS_CHARTS_SECTION: StatsChartsSection = {
......
......@@ -7,6 +7,7 @@ export interface ChartMarketItem {
date: string;
closing_price: string;
market_cap?: string;
tvl?: string | null;
}
export interface ChartTransactionResponse {
......
......@@ -11,6 +11,8 @@ export type HomeStats = {
static_gas_price: string | null;
market_cap: string;
network_utilization_percentage: number;
tvl: string | null;
rootstock_locked_btc?: string | null;
}
export type GasPrices = {
......
export type ChainIndicatorId = 'daily_txs' | 'coin_price' | 'market_cap';
export type ChainIndicatorId = 'daily_txs' | 'coin_price' | 'market_cap' | 'tvl';
......@@ -18,7 +18,7 @@ test.describe('all items', () => {
test.beforeEach(async({ page, mount }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
body: JSON.stringify(statsMock.withBtcLocked),
}));
component = await mount(
......
import { Grid } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import { route } from 'nextjs-routes';
......@@ -6,10 +7,12 @@ import { route } from 'nextjs-routes';
import config from 'configs/app';
import blockIcon from 'icons/block.svg';
import clockIcon from 'icons/clock-light.svg';
import bitcoinIcon from 'icons/coins/bitcoin.svg';
import gasIcon from 'icons/gas.svg';
import txIcon from 'icons/transactions.svg';
import walletIcon from 'icons/wallet.svg';
import useApiQuery from 'lib/api/useApiQuery';
import { WEI } from 'lib/consts';
import { HOMEPAGE_STATS } from 'stubs/stats';
import StatsGasPrices from './StatsGasPrices';
......@@ -39,6 +42,7 @@ const Stats = () => {
if (data) {
!data.gas_prices && itemsCount--;
data.rootstock_locked_btc && itemsCount++;
const isOdd = Boolean(itemsCount % 2);
const gasLabel = hasGasTracker && data.gas_prices ? <StatsGasPrices gasPrices={ data.gas_prices }/> : null;
......@@ -83,6 +87,15 @@ const Stats = () => {
isLoading={ isPlaceholderData }
/>
) }
{ data.rootstock_locked_btc && (
<StatsItem
icon={ bitcoinIcon }
title="BTC Locked in 2WP"
value={ `${ BigNumber(data.rootstock_locked_btc).div(WEI).dp(0).toFormat() } RBTC` }
_last={ isOdd ? lastItemTouchStyle : undefined }
isLoading={ isPlaceholderData }
/>
) }
</>
);
}
......
import { test, expect } from '@playwright/experimental-ct-react';
import { test as base, expect } from '@playwright/experimental-ct-react';
import type { Locator } from '@playwright/test';
import React from 'react';
import * as dailyTxsMock from 'mocks/stats/daily_txs';
import * as statsMock from 'mocks/stats/index';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
......@@ -12,6 +13,13 @@ import ChainIndicators from './ChainIndicators';
const STATS_API_URL = buildApiUrl('homepage_stats');
const TX_CHART_API_URL = buildApiUrl('homepage_chart_txs');
const test = base.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_HOMEPAGE_CHARTS', value: '["daily_txs","coin_price","market_cap","tvl"]' },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
]) as any,
});
test.describe('daily txs chart', () => {
let component: Locator;
......
......@@ -5,6 +5,7 @@ import type { TChainIndicator } from '../types';
import config from 'configs/app';
import globeIcon from 'icons/globe.svg';
import lockIcon from 'icons/lock.svg';
import txIcon from 'icons/transactions.svg';
import { sortByDateDesc } from 'ui/shared/chart/utils/sorts';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
......@@ -76,10 +77,34 @@ const marketPriceIndicator: TChainIndicator<'homepage_chart_market'> = {
},
};
const tvlIndicator: TChainIndicator<'homepage_chart_market'> = {
id: 'tvl',
title: 'Total value locked',
value: (stats) => '$' + Number(stats.tvl).toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }),
icon: <Icon as={ lockIcon } boxSize={ 6 } bgColor="#517FDB" borderRadius="base" color="white"/>,
// eslint-disable-next-line max-len
hint: 'Total value of digital assets locked or staked in a chain',
api: {
resourceName: 'homepage_chart_market',
dataFn: (response) => ([ {
items: response.chart_data
.map((item) => (
{
date: new Date(item.date),
value: item.tvl ? Number(item.tvl) : 0,
}))
.sort(sortByDateDesc),
name: 'TVL',
valueFormatter: (x: number) => '$' + x.toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }),
} ]),
},
};
const INDICATORS = [
dailyTxsIndicator,
coinPriceIndicator,
marketPriceIndicator,
tvlIndicator,
];
export default INDICATORS;
......@@ -6,6 +6,7 @@ import { apps as appsMock } from 'mocks/apps/apps';
import * as searchMock from 'mocks/search/index';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import * as app from 'playwright/utils/app';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import LayoutMainColumn from 'ui/shared/layout/components/MainColumn';
......@@ -182,7 +183,7 @@ test('search by tx hash +@mobile', async({ mount, page }) => {
});
test.describe('with apps', () => {
const MARKETPLACE_CONFIG_URL = buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || '';
const MARKETPLACE_CONFIG_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || '';
const extendedTest = test.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', value: MARKETPLACE_CONFIG_URL },
......
......@@ -6,12 +6,13 @@ import { buildExternalAssetFilePath } from 'configs/app/utils';
import { FOOTER_LINKS } from 'mocks/config/footerLinks';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import * as app from 'playwright/utils/app';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import Footer from './Footer';
const FOOTER_LINKS_URL = buildExternalAssetFilePath('NEXT_PUBLIC_FOOTER_LINKS', 'https://localhost:3000/footer-links.json') || '';
const FOOTER_LINKS_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_FOOTER_LINKS', 'https://localhost:3000/footer-links.json') || '';
const BACKEND_VERSION_API_URL = buildApiUrl('config_backend_version');
const INDEXING_ALERT_API_URL = buildApiUrl('homepage_indexing_status');
......
......@@ -6,10 +6,11 @@ import { FEATURED_NETWORKS_MOCK } from 'mocks/config/network';
import authFixture from 'playwright/fixtures/auth';
import contextWithEnvs, { createContextWithEnvs } from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import * as app from 'playwright/utils/app';
import Burger from './Burger';
const FEATURED_NETWORKS_URL = buildExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS', 'https://localhost:3000/featured-networks.json') || '';
const FEATURED_NETWORKS_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS', 'https://localhost:3000/featured-networks.json') || '';
const LOGO_URL = 'https://localhost:3000/my-logo.png';
base.use({ viewport: devices['iPhone 13 Pro'].viewport });
......
......@@ -21,7 +21,7 @@ const hooksConfig = {
},
};
const FEATURED_NETWORKS_URL = buildExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS', 'https://localhost:3000/featured-networks.json') || '';
const FEATURED_NETWORKS_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS', 'https://localhost:3000/config.json') || '';
const test = base.extend({
context: contextWithEnvs([
......
......@@ -5,6 +5,7 @@ import React from 'react';
import { buildExternalAssetFilePath } from 'configs/app/utils';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import * as app from 'playwright/utils/app';
import * as configs from 'playwright/utils/configs';
import NetworkLogo from './NetworkLogo';
......@@ -44,8 +45,8 @@ base.describe('placeholder logo', () => {
});
base.describe('custom logo', () => {
const LOGO_URL = buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO', 'https://localhost:3000/my-logo.png') || '';
const ICON_URL = buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON', 'https://localhost:3000/my-icon.png') || '';
const LOGO_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO', 'https://localhost:3000/my-logo.png') || '';
const ICON_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON', 'https://localhost:3000/my-icon.png') || '';
const test = base.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_NETWORK_LOGO', value: LOGO_URL },
......@@ -91,10 +92,10 @@ base.describe('custom logo', () => {
});
base.describe('custom logo with dark option -@default +@dark-mode', () => {
const LOGO_URL = buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO', 'https://localhost:3000/my-logo.png') || '';
const LOGO_URL_DARK = buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO_DARK', 'https://localhost:3000/my-logo.png') || '';
const ICON_URL = buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON', 'https://localhost:3000/my-icon.png') || '';
const ICON_URL_DARK = buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON_DARK', 'https://localhost:3000/my-icon.png') || '';
const LOGO_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO', 'https://localhost:3000/my-logo.png') || '';
const LOGO_URL_DARK = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO_DARK', 'https://localhost:3000/my-logo.png') || '';
const ICON_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON', 'https://localhost:3000/my-icon.png') || '';
const ICON_URL_DARK = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON_DARK', 'https://localhost:3000/my-icon.png') || '';
const test = base.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_NETWORK_LOGO', value: LOGO_URL },
......
......@@ -5,10 +5,11 @@ import { buildExternalAssetFilePath } from 'configs/app/utils';
import { FEATURED_NETWORKS_MOCK } from 'mocks/config/network';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import * as app from 'playwright/utils/app';
import NetworkMenu from './NetworkMenu';
const FEATURED_NETWORKS_URL = buildExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS', 'https://localhost:3000/featured-networks.json') || '';
const FEATURED_NETWORKS_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS', 'https://localhost:3000/featured-networks.json') || '';
const extendedTest = test.extend({
context: contextWithEnvs([
......
......@@ -8,6 +8,7 @@ import { apps as appsMock } from 'mocks/apps/apps';
import * as searchMock from 'mocks/search/index';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import * as app from 'playwright/utils/app';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import SearchBar from './SearchBar';
......@@ -275,7 +276,7 @@ test('recent keywords suggest +@mobile', async({ mount, page }) => {
});
base.describe('with apps', () => {
const MARKETPLACE_CONFIG_URL = buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || '';
const MARKETPLACE_CONFIG_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || '';
const test = base.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', value: MARKETPLACE_CONFIG_URL },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment