Commit 24d63c9d authored by tom goriunov's avatar tom goriunov Committed by GitHub

Celo: show gas fees paid in ERC20 tokens (#2061)

* component for displaying tx fee

* config for celo alfajores

* display tx fee and gas price in celo token

* adjustments for stability fee

* clean up

* update screenshots

* change envs for demo

* more screenshot updates

* Removing unneeded network id var from review deploy

* rollback envs for demo

* fix currency symbol in tx table header

* lol 🤦‍♂️

---------
Co-authored-by: default avatarNick Zenchik <n.zenchik@gmail.com>
parent 45eb8741
......@@ -12,6 +12,7 @@ on:
- none
- arbitrum
- base
- celo_alfajores
- gnosis
- eth
- eth_sepolia
......
......@@ -339,7 +339,9 @@
"main",
"main.L2",
"localhost",
"arbitrum",
"base",
"celo_alfajores",
"gnosis",
"eth",
"eth_goerli",
......
......@@ -15,6 +15,7 @@ const chain = Object.freeze({
secondaryCoin: {
symbol: getEnvValue('NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL'),
},
hasMultipleGasCurrencies: getEnvValue('NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES') === 'true',
tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC',
rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'),
isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true',
......
# Set of ENVs for Celo Alfajjores network explorer
# https://celo-alfajores.blockscout.com/
# app configuration
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
# blockchain parameters
NEXT_PUBLIC_NETWORK_NAME=Celo Alfajores
NEXT_PUBLIC_NETWORK_SHORT_NAME=Alfajores
NEXT_PUBLIC_NETWORK_ID=44787
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Celo
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=CELO
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES=true
NEXT_PUBLIC_NETWORK_RPC_URL=https://alfajores-forno.celo-testnet.org
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_IS_TESTNET=true
# api configuration
NEXT_PUBLIC_API_HOST=celo-alfajores.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(0,0,0,1)
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(252,255,82,1)
## sidebar
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/celo-logo-light.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/celo-logo-dark.svg
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/celo-icon-light.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/celo-icon-dark.svg
## footer
##views
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
## views
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
# app features
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_GAS_TRACKER_ENABLED=false
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
# NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.k8s-dev.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs-test.k8s-dev.blockscout.com
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/celo.png
......@@ -491,6 +491,7 @@ const schema = yup
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(),
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: yup.number().integer().positive(),
NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL: yup.string(),
NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES: yup.boolean(),
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup.string<NetworkVerificationType>().oneOf([ 'validation', 'mining' ]),
NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME: yup.string(),
NEXT_PUBLIC_IS_TESTNET: yup.boolean(),
......
......@@ -47,6 +47,7 @@ NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Explorer','baseUrl':'https://example.com/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL=GNO
NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES=true
NEXT_PUBLIC_NETWORK_ICON=https://example.com/icon.png
NEXT_PUBLIC_NETWORK_ICON_DARK=https://example.com/icon.png
NEXT_PUBLIC_NETWORK_LOGO=https://example.com/logo.png
......
......@@ -68,7 +68,6 @@ frontend:
NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']"
NEXT_PUBLIC_NETWORK_RPC_URL: https://eth-sepolia.public.blastapi.io
NEXT_PUBLIC_NETWORK_ID: '11155111'
NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]"
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_FEATURED_APP: zkbob-wallet
......@@ -77,10 +76,7 @@ frontend:
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']"
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS: "['top_accounts']"
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED: true
NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS: "['value','fee_currency','gas_price','gas_fees','burnt_fees']"
NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS: "['fee_per_gas']"
NEXT_PUBLIC_USE_NEXT_JS_PROXY: true
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
NEXT_PUBLIC_HAS_USER_OPS: true
......@@ -89,7 +85,6 @@ frontend:
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true
NEXT_PUBLIC_AD_BANNER_PROVIDER: slise
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: true
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']"
envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
......
......@@ -91,6 +91,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
| NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | Network currency symbol | - | - | `ETH` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | `string` | Network secondary coin symbol. | - | - | `GNO` | v1.29.0+ |
| NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES | `boolean` | Set to `true` for networks where users can pay transaction fees in either the native coin or ERC-20 tokens. | - | `false` | `true` | v1.33.0+ |
| NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` or `mining` | Verification type in the network | - | `mining` | `validation` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME | `string` | Name of the standard for creating tokens | - | `ERC` | `BEP` | v1.31.0+ |
| NEXT_PUBLIC_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` | v1.0.x+ |
......
<svg viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.139 20H4.83a3.847 3.847 0 0 1-3.846-3.845V5.39a.769.769 0 0 1 .77-.769h15.384a3.847 3.847 0 0 1 3.846 3.845v7.69A3.844 3.844 0 0 1 17.139 20ZM2.524 6.16v9.996a2.307 2.307 0 0 0 2.307 2.307H17.14a2.308 2.308 0 0 0 2.308-2.307v-7.69a2.306 2.306 0 0 0-2.308-2.306H2.524Z" fill="currentColor"/>
<path d="M18.677 6.159a.77.77 0 0 1-.769-.77V3.276a1.79 1.79 0 0 0-.6-1.438 1.493 1.493 0 0 0-1.284-.246l-12.9 2.93a.77.77 0 0 0-.6.769.769.769 0 0 1-1.539 0A2.306 2.306 0 0 1 2.778 3.02L15.685.091a3 3 0 0 1 2.585.546 3.338 3.338 0 0 1 1.177 2.638V5.39a.77.77 0 0 1-.77.769ZM20.216 15.386H14.83a3.077 3.077 0 0 1-2.175-5.25c.577-.578 1.36-.902 2.175-.902h5.385a.77.77 0 0 1 .769.77v4.613a.77.77 0 0 1-.77.769Zm-5.385-4.614a1.539 1.539 0 1 0 0 3.076h4.616v-3.076H14.83Z" fill="currentColor"/>
<path d="M17.139 20H4.83a3.847 3.847 0 0 1-3.846-3.845V5.39a.769.769 0 0 1 .77-.769h15.384a3.847 3.847 0 0 1 3.846 3.845v7.69A3.844 3.844 0 0 1 17.139 20ZM2.524 6.16v9.996a2.307 2.307 0 0 0 2.307 2.307H17.14a2.308 2.308 0 0 0 2.308-2.307v-7.69A2.306 2.306 0 0 0 17.14 6.16H2.524Z" fill="currentColor"/>
<path d="M18.677 6.159a.77.77 0 0 1-.769-.77V3.276a1.79 1.79 0 0 0-.6-1.438 1.493 1.493 0 0 0-1.284-.246l-12.9 2.93a.77.77 0 0 0-.6.769.769.769 0 0 1-1.539 0A2.306 2.306 0 0 1 2.778 3.02L15.685.091a3 3 0 0 1 2.585.546 3.338 3.338 0 0 1 1.177 2.638V5.39a.77.77 0 0 1-.77.769Zm1.539 9.227H14.83a3.077 3.077 0 0 1-2.175-5.25 3.074 3.074 0 0 1 2.175-.902h5.385a.77.77 0 0 1 .769.77v4.613a.77.77 0 0 1-.77.769Zm-5.385-4.614a1.539 1.539 0 1 0 0 3.076h4.616v-3.076H14.83Z" fill="currentColor"/>
</svg>
......@@ -299,7 +299,7 @@ export const stabilityTx: Transaction = {
decimals: '18',
exchange_rate: '123.567',
holders: '92',
icon_url: null,
icon_url: 'https://example.com/icon.png',
name: 'Stability Gas',
symbol: 'GAS',
total_supply: '10000000000000000000000000',
......@@ -321,6 +321,24 @@ export const stabilityTx: Transaction = {
},
};
export const celoTxn: Transaction = {
...base,
celo: {
gas_token: {
address: '0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1',
circulating_market_cap: null,
decimals: '18',
exchange_rate: '0.42',
holders: '205738',
icon_url: 'https://example.com/icon.png',
name: 'Celo Dollar',
symbol: 'cUSD',
total_supply: '7145754483836626799435133',
type: 'ERC-20',
},
},
};
export const base2 = {
...base,
hash: '0x02d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193',
......
......@@ -77,6 +77,10 @@ export type Transaction = {
validator_address: AddressParam;
validator_fee: string;
};
// Celo fields
celo?: {
gas_token: TokenInfo<'ERC-20'> | null;
};
// zkEvm fields
zkevm_verify_hash?: string;
zkevm_batch_number?: number;
......
......@@ -17,7 +17,7 @@ import { currencyUnits } from 'lib/units';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
import TxFee from 'ui/shared/tx/TxFee';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import TxType from 'ui/txs/TxType';
......@@ -96,13 +96,7 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<Skeleton isLoaded={ !isLoading } display="flex" whiteSpace="pre" my="3px">
<Text as="span">Fee </Text>
{ tx.stability_fee ? (
<TxFeeStability data={ tx.stability_fee } accuracy={ 5 } color="text_secondary" hideUsd/>
) : (
<Text as="span" variant="secondary">
{ tx.fee.value ? `${ getValueWithUnit(tx.fee.value).dp(5).toFormat() } ${ currencyUnits.ether }` : '-' }
</Text>
) }
<TxFee tx={ tx } accuracy={ 5 } color="text_secondary"/>
</Skeleton>
) }
</Flex>
......
......@@ -16,7 +16,7 @@ import { currencyUnits } from 'lib/units';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
import TxFee from 'ui/shared/tx/TxFee';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import TxType from 'ui/txs/TxType';
......@@ -76,18 +76,14 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
/>
{ !config.UI.views.tx.hiddenFields?.value && (
<Skeleton isLoaded={ !isLoading } mb={ 2 } fontSize="sm" w="fit-content">
<Text as="span">Value { currencyUnits.ether } </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text>
<Text as="span">Value </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() } { currencyUnits.ether }</Text>
</Skeleton>
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<Skeleton isLoaded={ !isLoading } fontSize="sm" w="fit-content" display="flex" whiteSpace="pre">
<Text as="span">Fee { !config.UI.views.tx.hiddenFields?.fee_currency ? `${ currencyUnits.ether } ` : '' }</Text>
{ tx.stability_fee ? (
<TxFeeStability data={ tx.stability_fee } accuracy={ 5 } color="text_secondary" hideUsd/>
) : (
<Text as="span" variant="secondary">{ tx.fee.value ? getValueWithUnit(tx.fee.value).dp(5).toFormat() : '-' }</Text>
) }
<Text as="span">Fee </Text>
<TxFee tx={ tx } accuracy={ 5 } color="text_secondary"/>
</Skeleton>
) }
</Box>
......
import { Box, Text, chakra, Skeleton } from '@chakra-ui/react';
import { chakra, Skeleton } from '@chakra-ui/react';
import React from 'react';
import getCurrencyValue from 'lib/getCurrencyValue';
......@@ -23,20 +23,20 @@ const CurrencyValue = ({ value, currency = '', decimals, exchangeRate, className
if (value === undefined || value === null) {
return (
<Box as="span" className={ className }>
<Text>N/A</Text>
</Box>
<chakra.span className={ className }>
-
</chakra.span>
);
}
const { valueStr: valueResult, usd: usdResult } = getCurrencyValue({ value, accuracy, accuracyUsd, exchangeRate, decimals });
return (
<Box as="span" className={ className } display="inline-flex" rowGap={ 3 } columnGap={ 1 }>
<Text display="inline-block">
<chakra.span className={ className } display="inline-flex" rowGap={ 3 } columnGap={ 1 }>
<chakra.span display="inline-block">
{ valueResult }{ currency ? ` ${ currency }` : '' }
</Text>
{ usdResult && <Text as="span" variant="secondary" fontWeight={ 400 }>(${ usdResult })</Text> }
</Box>
</chakra.span>
{ usdResult && <chakra.span color="text_secondary" fontWeight={ 400 }>(${ usdResult })</chakra.span> }
</chakra.span>
);
};
......
import { Box, DarkMode, Popover, PopoverBody, PopoverContent, PopoverTrigger, Portal, useColorModeValue, Flex, PopoverArrow } from '@chakra-ui/react';
import { Box, DarkMode, PopoverBody, PopoverContent, PopoverTrigger, Portal, useColorModeValue, Flex, PopoverArrow } from '@chakra-ui/react';
import React from 'react';
import Popover from 'ui/shared/chakra/Popover';
import * as EntityBase from 'ui/shared/entities/base/components';
import type { ContentProps } from './AddressEntity';
......@@ -23,7 +24,7 @@ const AddressEntityContentProxy = (props: ContentProps) => {
const implementationName = implementations.length === 1 && implementations[0].name ? implementations[0].name : undefined;
return (
<Popover trigger="hover" isLazy>
<Popover trigger="hover" isLazy gutter={ 8 }>
<PopoverTrigger>
<Box display="inline-flex" w="100%">
<EntityBase.Content
......
import React from 'react';
import * as txMock from 'mocks/txs/tx';
import { test, expect } from 'playwright/lib';
import TxFee from './TxFee';
test.use({ viewport: { width: 300, height: 100 } });
test('base view', async({ render }) => {
const component = await render(<TxFee tx={ txMock.base } withUsd/>);
await expect(component).toHaveScreenshot();
});
test('no usd value', async({ render }) => {
const component = await render(<TxFee tx={ txMock.base } accuracy={ 3 }/>);
await expect(component).toHaveScreenshot();
});
test('celo gas token', async({ render, mockAssetResponse }) => {
await mockAssetResponse(txMock.celoTxn.celo?.gas_token?.icon_url as string, './playwright/mocks/image_svg.svg');
const component = await render(<TxFee tx={ txMock.celoTxn } withUsd accuracyUsd={ 3 }/>);
await expect(component).toHaveScreenshot();
});
test('stability token', async({ render, mockAssetResponse }) => {
await mockAssetResponse(txMock.stabilityTx.stability_fee?.token.icon_url as string, './playwright/mocks/image_svg.svg');
const component = await render(<TxFee tx={ txMock.stabilityTx } withUsd accuracyUsd={ 3 }/>);
await expect(component).toHaveScreenshot();
});
import { chakra, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import config from 'configs/app';
import getCurrencyValue from 'lib/getCurrencyValue';
import { currencyUnits } from 'lib/units';
import CurrencyValue from 'ui/shared/CurrencyValue';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
interface Props {
className?: string;
isLoading?: boolean;
tx: Transaction;
withCurrency?: boolean;
withUsd?: boolean;
accuracy?: number;
accuracyUsd?: number;
}
const TxFee = ({ className, tx, accuracy, accuracyUsd, isLoading, withCurrency = true, withUsd }: Props) => {
if (tx.celo?.gas_token) {
const token = tx.celo.gas_token;
const { valueStr, usd } = getCurrencyValue({
value: tx.fee.value || '0',
exchangeRate: token.exchange_rate,
decimals: token.decimals,
accuracy,
accuracyUsd,
});
return (
<Skeleton whiteSpace="pre-wrap" wordBreak="break-word" isLoaded={ !isLoading } display="flex" flexWrap="wrap" className={ className }>
<span>{ valueStr } </span>
<TokenEntity token={ token } noCopy onlySymbol w="auto" ml={ 1 }/>
{ usd && withUsd && <chakra.span color="text_secondary"> (${ usd })</chakra.span> }
</Skeleton>
);
}
if (tx.stability_fee) {
const token = tx.stability_fee.token;
const { valueStr, usd } = getCurrencyValue({
value: tx.stability_fee.total_fee,
exchangeRate: token.exchange_rate,
decimals: token.decimals,
accuracy,
accuracyUsd,
});
return (
<Skeleton whiteSpace="pre" isLoaded={ !isLoading } display="flex" className={ className }>
<span>{ valueStr } </span>
{ valueStr !== '0' && <TokenEntity token={ token } noCopy onlySymbol w="auto" ml={ 1 }/> }
{ usd && withUsd && <chakra.span color="text_secondary"> (${ usd })</chakra.span> }
</Skeleton>
);
}
const showCurrency = withCurrency && !config.UI.views.tx.hiddenFields?.fee_currency;
return (
<CurrencyValue
value={ tx.fee.value }
currency={ showCurrency ? currencyUnits.ether : '' }
exchangeRate={ withUsd ? tx.exchange_rate : null }
accuracy={ accuracy }
accuracyUsd={ accuracyUsd }
flexWrap="wrap"
className={ className }
isLoading={ isLoading }
/>
);
};
export default React.memo(chakra(TxFee));
import { Skeleton, chakra } from '@chakra-ui/react';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import type { ExcludeUndefined } from 'types/utils';
import getCurrencyValue from 'lib/getCurrencyValue';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
interface Props {
data: ExcludeUndefined<Transaction['stability_fee']>;
isLoading?: boolean;
hideUsd?: boolean;
accuracy?: number;
className?: string;
}
const TxFeeStability = ({ data, isLoading, hideUsd, accuracy, className }: Props) => {
const { valueStr, usd } = getCurrencyValue({
value: data.total_fee,
exchangeRate: data.token.exchange_rate,
decimals: data.token.decimals,
accuracy,
});
return (
<Skeleton whiteSpace="pre" isLoaded={ !isLoading } display="flex" className={ className }>
<span>{ valueStr } </span>
{ valueStr !== '0' && <TokenEntity token={ data.token } noCopy onlySymbol w="auto" ml={ 1 }/> }
{ usd && !hideUsd && <chakra.span color="text_secondary"> (${ usd })</chakra.span> }
</Skeleton>
);
};
export default React.memo(chakra(TxFeeStability));
......@@ -14,6 +14,7 @@ import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData';
import RawInputData from 'ui/shared/RawInputData';
import TxFee from 'ui/shared/tx/TxFee';
import TxDetailsGasPrice from 'ui/tx/details/TxDetailsGasPrice';
import TxDetailsOther from 'ui/tx/details/TxDetailsOther';
......@@ -80,11 +81,7 @@ const TxDetailsWrapped = ({ data }: Props) => {
Transaction fee
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.fee.value }
currency={ currencyUnits.ether }
flexWrap="wrap"
/>
<TxFee tx={ data } withUsd/>
</DetailsInfoItem.Value>
</>
) }
......
......@@ -2,21 +2,47 @@ import { Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { TokenInfo } from 'types/api/token';
import config from 'configs/app';
import { WEI, WEI_IN_GWEI } from 'lib/consts';
import { currencyUnits } from 'lib/units';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
interface Props {
gasToken?: TokenInfo<'ERC-20'> | null;
gasPrice: string | null;
isLoading?: boolean;
}
const TxDetailsGasPrice = ({ gasPrice, isLoading }: Props) => {
const TxDetailsGasPrice = ({ gasPrice, gasToken, isLoading }: Props) => {
if (config.UI.views.tx.hiddenFields?.gas_price || !gasPrice) {
return null;
}
const content = (() => {
if (gasToken) {
return (
<Skeleton isLoaded={ !isLoading } display="flex">
<span>{ BigNumber(gasPrice).dividedBy(WEI).toFixed() }</span>
<TokenEntity token={ gasToken } noCopy onlySymbol w="auto" ml={ 1 }/>
</Skeleton>
);
}
return (
<>
<Skeleton isLoaded={ !isLoading } mr={ 1 }>
{ BigNumber(gasPrice).dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>({ BigNumber(gasPrice).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</span>
</Skeleton>
</>
);
})();
return (
<>
<DetailsInfoItem.Label
......@@ -26,12 +52,7 @@ const TxDetailsGasPrice = ({ gasPrice, isLoading }: Props) => {
Gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } mr={ 1 }>
{ BigNumber(gasPrice).dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>({ BigNumber(gasPrice).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</span>
</Skeleton>
{ content }
</DetailsInfoItem.Value>
</>
);
......
......@@ -46,7 +46,7 @@ import RawInputData from 'ui/shared/RawInputData';
import StatusTag from 'ui/shared/statusTag/StatusTag';
import TxStatus from 'ui/shared/statusTag/TxStatus';
import TextSeparator from 'ui/shared/TextSeparator';
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
import TxFee from 'ui/shared/tx/TxFee';
import Utilization from 'ui/shared/Utilization/Utilization';
import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps';
import TxDetailsActions from 'ui/tx/details/txDetailsActions/TxDetailsActions';
......@@ -555,17 +555,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
Transaction fee
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.stability_fee ? (
<TxFeeStability data={ data.stability_fee } isLoading={ isLoading }/>
) : (
<CurrencyValue
value={ data.fee.value }
currency={ config.UI.views.tx.hiddenFields?.fee_currency ? '' : currencyUnits.ether }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
isLoading={ isLoading }
/>
) }
<TxFee tx={ data } isLoading={ isLoading } withUsd/>
</DetailsInfoItem.Value>
</>
) }
......@@ -606,7 +596,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
</>
) }
<TxDetailsGasPrice gasPrice={ data.gas_price } isLoading={ isLoading }/>
<TxDetailsGasPrice gasPrice={ data.gas_price } gasToken={ data.celo?.gas_token } isLoading={ isLoading }/>
<TxDetailsFeePerGas txFee={ data.fee.value } gasUsed={ data.gas_used } isLoading={ isLoading }/>
......
......@@ -9,11 +9,10 @@ import { route } from 'nextjs-routes';
import config from 'configs/app';
import getValueWithUnit from 'lib/getValueWithUnit';
import { currencyUnits } from 'lib/units';
import CurrencyValue from 'ui/shared/CurrencyValue';
import BlobEntity from 'ui/shared/entities/blob/BlobEntity';
import LinkInternal from 'ui/shared/links/LinkInternal';
import TextSeparator from 'ui/shared/TextSeparator';
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
import TxFee from 'ui/shared/tx/TxFee';
import Utilization from 'ui/shared/Utilization/Utilization';
const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => {
......@@ -60,20 +59,7 @@ const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => {
{ (tx.stability_fee !== undefined || tx.fee.value !== null) && (
<>
<Text { ...sectionTitleProps }>Transaction fee</Text>
{ tx.stability_fee ? (
<TxFeeStability data={ tx.stability_fee }/>
) : (
<Flex>
<CurrencyValue
value={ tx.fee.value }
currency={ config.UI.views.tx.hiddenFields?.fee_currency ? '' : currencyUnits.ether }
exchangeRate={ tx.exchange_rate }
accuracyUsd={ 2 }
flexWrap="wrap"
rowGap={ 0 }
/>
</Flex>
) }
<TxFee tx={ tx } withUsd accuracyUsd={ 2 } rowGap={ 0 }/>
</>
) }
</Box>
......
......@@ -17,7 +17,7 @@ import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
import TxFee from 'ui/shared/tx/TxFee';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import TxType from 'ui/txs/TxType';
......@@ -111,14 +111,7 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI
{ (tx.stability_fee !== undefined || tx.fee.value !== null) && (
<>
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Fee</Skeleton>
{ tx.stability_fee ? (
<TxFeeStability data={ tx.stability_fee } isLoading={ isLoading } hideUsd/>
) : (
<Skeleton isLoaded={ !isLoading } display="inline-block" variant="text_secondary" whiteSpace="pre">
{ getValueWithUnit(tx.fee.value || 0).toFormat() }
{ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ currencyUnits.ether }` }
</Skeleton>
) }
<TxFee tx={ tx } isLoading={ isLoading }/>
</>
) }
</Flex>
......
......@@ -43,6 +43,10 @@ const TxsTable = ({
}: Props) => {
const { cutRef, renderedItemsNum } = useLazyRenderedList(txs, !isLoading);
const feeCurrency = config.UI.views.tx.hiddenFields?.fee_currency || config.chain.hasMultipleGasCurrencies ?
'' :
' ' + currencyUnits.ether;
return (
<AddressHighlightProvider>
<Table variant="simple" minWidth="950px" size="xs">
......@@ -68,7 +72,7 @@ const TxsTable = ({
<Link onClick={ sort('fee') } display="flex" justifyContent="end">
{ sorting === 'fee-asc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(-90deg)"/> }
{ sorting === 'fee-desc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(90deg)"/> }
{ `Fee${ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ currencyUnits.ether }` }` }
{ `Fee${ feeCurrency }` }
</Link>
</Th>
) }
......
......@@ -17,7 +17,7 @@ import CurrencyValue from 'ui/shared/CurrencyValue';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
import TxFee from 'ui/shared/tx/TxFee';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
......@@ -109,12 +109,13 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement,
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<Td isNumeric>
{ /* eslint-disable-next-line no-nested-ternary */ }
{ tx.stability_fee ? (
<TxFeeStability data={ tx.stability_fee } isLoading={ isLoading } accuracy={ 8 } justifyContent="end" hideUsd/>
) : (
tx.fee.value ? <CurrencyValue value={ tx.fee.value } accuracy={ 8 } isLoading={ isLoading }/> : '-'
) }
<TxFee
tx={ tx }
accuracy={ 8 }
isLoading={ isLoading }
withCurrency={ Boolean(tx.celo || tx.stability_fee) }
justifyContent="end"
/>
</Td>
) }
</Tr>
......
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