Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/deploy-demo/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ _Note:_ In the command output, format all URLs as clickable Markdown links: `[Li
**c. `build_image`** — whether to build & publish a fresh image (default `true`).
- The image is preset-agnostic, so **re-pointing an existing demo to a different chain does not need a rebuild**. If the user asks to *redeploy / re-point / switch an existing demo to another preset* ("redeploy it for celo", "switch the demo to base without rebuilding", "re-point to gnosis"), pass `-f build_image=false` — this skips the multi-minute image build and just re-runs the deploy (~1–2 min), changing `ENVS_PRESET` so the pods roll.
- For a first/normal deploy, skip the flag (default `true`, builds fresh).
- **Caveat:** `build_image=false` only works if an image for this branch + `variant` already exists (a prior successful build). If unsure, prefer building. Keep `variant` the same as the original deploy when re-pointing — the image tag and namespace are `<variant>-<branch-slug>`.
- **Caveat:** `build_image=false` only works if an image for this branch already exists (a prior successful build). If unsure, prefer building. The image tag is `review-<branch-slug>` — **variant-independent** (the image is both preset- and variant-agnostic). So an image built under either variant can be reused by the other: e.g. after a `review` build you can deploy `review-2` with `build_image=false`. Only the namespace/hostname (`<variant>-<branch-slug>`) differ per variant, so the two demos coexist.

### 4. Trigger the workflow and capture the run ID

Expand Down
3 changes: 3 additions & 0 deletions .env.extra
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
#
# This file IS committed — keep it empty on main. Do NOT put secrets here (use .env.secrets,
# git-ignored). For purely-local, personal overrides use .env.local (git-ignored).

NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED=true
NEXT_PUBLIC_INTERCHAIN_INDEXER_API_HOST=https://interchain-indexer.k8s-dev.blockscout.com
Comment thread
tom2drum marked this conversation as resolved.
6 changes: 5 additions & 1 deletion .github/workflows/deploy-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ on:
- filecoin
- garnet
- gnosis
- gnosis_chiado
- immutable
- mega_eth
- multichain
Expand Down Expand Up @@ -85,8 +86,11 @@ jobs:
if: ${{ inputs.build_image }}
uses: './.github/workflows/publish-image.yml'
with:
# Variant-independent tag: the image is preset- AND variant-agnostic,
# so one image serves both the `review` and `review-2` demos. This lets a
# deploy for one variant reuse (build_image=false) an image built under the other.
tags: |
type=raw,value=${{ inputs.variant }}-${{ needs.make_slug.outputs.REF_SLUG }}
type=raw,value=review-${{ needs.make_slug.outputs.REF_SLUG }}
platforms: linux/amd64
secrets: inherit

Expand Down
1 change: 1 addition & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@
"filecoin",
"garnet",
"gnosis",
"gnosis_chiado",
"immutable",
"mega_eth",
"multichain",
Expand Down
4 changes: 3 additions & 1 deletion deploy/scripts/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
export_envs_from_file() {
local file="$1"
[ -f "$file" ] || return 0
while IFS='=' read -r name value; do
# `|| [ -n "$name" ]` keeps the final line even when the file has no trailing
# newline — otherwise `read` returns non-zero at EOF and silently drops it.
while IFS='=' read -r name value || [ -n "$name" ]; do
if [[ -n "$name" && "$name" != \#* ]]; then
export "$name"="$value"
fi
Expand Down
1 change: 1 addition & 0 deletions deploy/tools/envs-validator/schema_multichain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const schema = yup
.concat(uiSchemas.footerSchema)
.concat(uiSchemas.miscSchema)
.concat(featuresSchemas.adsSchema)
.concat(featuresSchemas.crossChainTxsSchema)
.concat(featuresSchemas.defiDropdownSchema)
.concat(featuresSchemas.multichainButtonSchema)
.concat(featuresSchemas.userOpsSchema)
Expand Down
2 changes: 1 addition & 1 deletion deploy/values/review-2/values.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ frontend:
replicaCount: 1
image:
repository: ghcr.io/blockscout/frontend-private
tag: review-2-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}
tag: review-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}
pullPolicy: Always
ingress:
enabled: true
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@blockscout/api-types": "0.0.1-beta.cf13c2de97",
"@blockscout/bens-types": "1.7.1",
"@blockscout/contracts-info-types": "1.5.2",
"@blockscout/interchain-indexer-types": "1.3.0",
"@blockscout/interchain-indexer-types": "1.5.0-beta3",
"@blockscout/multichain-aggregator-types": "2.1.6",
"@blockscout/points-types": "1.4.1",
"@blockscout/stats-types": "2.11.1",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 25 additions & 2 deletions src/features/chain-stats/pages/details/ChainStatsDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import useChartQuery from 'src/features/chain-stats/hooks/useChartQuery';
import ChainStatsDetailsCrossChainTxs from 'src/features/cross-chain-txs/components/ChainStatsDetailsCrossChainTxs';
import useCrossChainChartQuery from 'src/features/cross-chain-txs/hooks/useCrossChainChartQuery';
import { CROSS_CHAIN_TXS_CHARTS } from 'src/features/cross-chain-txs/utils/chain-stats';
import multichainConfig from 'src/features/multichain/chains-config';
import useRoutedChainSelect from 'src/features/multichain/hooks/useRoutedChainSelect';

import config from 'src/config';
import isCustomAppError from 'src/shared/errors/is-custom-app-error';
Expand Down Expand Up @@ -47,10 +49,29 @@ const ChainStatsDetails = () => {
counterPartyChainIdsFromQuery.length > 0 ? counterPartyChainIdsFromQuery : [ ALL_OPTION.value ],
);

const crossChainTxsChart = config.features.crossChainTxs.isEnabled ? CROSS_CHAIN_TXS_CHARTS.find((chart) => chart.id === id) : undefined;
const chainIdsWithCrossChain = React.useMemo(() => {
return multichainConfig()?.chains
.filter((chain) => chain.app_config.features.crossChainTxs.isEnabled)
.map((chain) => chain.id);
}, []);

const chainSelectCrossChain = useRoutedChainSelect({
chainIds: chainIdsWithCrossChain,
});

const chainData = React.useMemo(() => {
const chainId = chainSelectCrossChain.value?.[0];
if (chainId) {
return multichainConfig()?.chains.find(({ id }) => id === chainId);
}
}, [ chainSelectCrossChain.value ]);

const crossChainTxsChart = (chainData?.app_config ?? config).features.crossChainTxs.isEnabled ?
CROSS_CHAIN_TXS_CHARTS.find((chart) => chart.id === id) :
undefined;

const queryBase = useChartQuery({ id, resolution, interval, enabled: !crossChainTxsChart });
const queryCrossChain = useCrossChainChartQuery({ id, interval, counterPartyChainIds, enabled: Boolean(crossChainTxsChart) });
const queryCrossChain = useCrossChainChartQuery({ id, interval, counterPartyChainIds, enabled: Boolean(crossChainTxsChart), chainData });

const query = crossChainTxsChart ? queryCrossChain : queryBase;
const isInitialLoading = useIsInitialLoading(query.isPlaceholderData);
Expand Down Expand Up @@ -108,6 +129,8 @@ const ChainStatsDetails = () => {
<ChainStatsDetailsCrossChainTxs
chart={ crossChainTxsChart }
data={ query.data?.data }
baseChain={ multichainConfig() ? chainData : undefined }
baseChainSelectProps={ multichainConfig() ? chainSelectCrossChain : undefined }
isLoading={ query.isPlaceholderData }
isError={ query.isError }
isInitialLoading={ isInitialLoading }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import { Flex, HStack, VStack, chakra } from '@chakra-ui/react';
import React from 'react';

import type { ChainStatsChart, StatsIntervalIds } from 'src/features/chain-stats/types/client';
import type { ClusterChainConfig } from 'src/features/multichain/types/client';
import type { SankeyChartData } from 'src/toolkit/components/charts/sankey/types';

import useApiQuery from 'src/api/hooks/useApiQuery';

import ChartIntervalSelect from 'src/features/chain-stats/components/ChartIntervalSelect';
import multichainConfig from 'src/features/multichain/chains-config';
import ChainSelectMultichain from 'src/features/multichain/components/ChainSelect';
import type useRoutedChainSelect from 'src/features/multichain/hooks/useRoutedChainSelect';

import config from 'src/config';
import * as mixpanel from 'src/services/mixpanel';
Expand All @@ -22,6 +26,8 @@ import { SankeyChartContent } from 'src/toolkit/components/charts/sankey/SankeyC
interface Props {
chart: ChainStatsChart;
data?: SankeyChartData;
baseChain?: ClusterChainConfig;
baseChainSelectProps?: ReturnType<typeof useRoutedChainSelect>;
isLoading: boolean;
isError: boolean;
isInitialLoading: boolean;
Expand All @@ -34,6 +40,8 @@ interface Props {
const ChainStatsDetailsCrossChainTxs = ({
chart,
data,
baseChain,
baseChainSelectProps,
isLoading,
isError,
isInitialLoading,
Expand All @@ -50,44 +58,84 @@ const ChainStatsDetailsCrossChainTxs = ({
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Share chart', Info: chart.id });
}, [ chart.id ]);

const chainsConfig = React.useMemo(() => {
const counterPartyChainsConfig = React.useMemo(() => {
return chainsQuery.data?.items
.filter((chain) => chain.id !== config.chain.id)
.filter((chain) => chain.id !== (baseChain?.app_config ?? config).chain.id)
.map((chain) => ({
id: chain.id,
name: chain.name,
logo: chain.logo,
explorer_url: chain.explorer_url,
})) || [];
}, [ chainsQuery.data ]);

const chainSelect = (
<ChainSelect
value={ counterPartyChainIds }
onValueChange={ onCounterPartyChainIdsChange }
chainsConfig={ chainsConfig }
withAllOption
loading={ chainsQuery.isLoading }
multiple
/>
);
}, [ baseChain?.app_config, chainsQuery.data?.items ]);

const baseChainIds = React.useMemo(() => {
return multichainConfig()?.chains
.filter((chain) => chain.app_config.features.crossChainTxs.isEnabled)
.map((chain) => chain.id);
}, []);

const chainFilter = (() => {
const chainNameElement = <chakra.span fontWeight="medium" color="text.primary">{ config.chain.name }</chakra.span>;
const counterPartyChainSelect = (
<ChainSelect
value={ counterPartyChainIds }
onValueChange={ onCounterPartyChainIdsChange }
chainsConfig={ counterPartyChainsConfig }
withAllOption
loading={ chainsQuery.isLoading }
multiple
/>
);

const baseChainSelect = baseChainSelectProps ? (
<ChainSelectMultichain
value={ baseChainSelectProps.value }
onValueChange={ baseChainSelectProps.onValueChange }
loading={ chainsQuery.isLoading }
chainIds={ baseChainIds }
/>
) : null;

const chainName = <chakra.span fontWeight="medium" color="text.primary">{ (baseChain?.app_config ?? config).chain.name }</chakra.span>;

if (chart.id === 'outgoing-messages-paths') {
if (baseChainSelect) {
return (
<HStack>
{ baseChainSelect }
<span>to</span>
{ counterPartyChainSelect }
</HStack>
);
}

return (
<HStack color="text.secondary">
<Skeleton loading={ isInitialLoading }><span>From { chainNameElement } to</span></Skeleton>
{ chainSelect }
<Skeleton loading={ isInitialLoading }>
<span>From { chainName } to </span>
</Skeleton>
{ counterPartyChainSelect }
</HStack>
);
}

if (chart.id === 'incoming-messages-paths') {
if (baseChainSelect) {
return (
<HStack>
{ counterPartyChainSelect }
<span>to</span>
{ baseChainSelect }
</HStack>
);
}

return (
<HStack color="text.secondary">
{ chainSelect }
<Skeleton loading={ isInitialLoading }><span>to { chainNameElement }</span></Skeleton>
{ counterPartyChainSelect }
<Skeleton loading={ isInitialLoading }>
<span>to { chainName }</span>
</Skeleton>
</HStack>
);
}
Expand All @@ -101,7 +149,7 @@ const ChainStatsDetailsCrossChainTxs = ({
}

return counterPartyChainIds.map((chainId) => {
return chainsConfig.find((chain) => chain.id === chainId)?.name || `Chain ${ chainId }`;
return counterPartyChainsConfig.find((chain) => chain.id === chainId)?.name || `Chain ${ chainId }`;
}).join(', ');
})();

Expand Down Expand Up @@ -130,11 +178,11 @@ const ChainStatsDetailsCrossChainTxs = ({
<VStack gap={ 1 } color="text.secondary" textStyle="xs" alignItems="flex-start" mt={{ base: 3, lg: 6 }}>
<Skeleton loading={ isInitialLoading }>
<chakra.span fontWeight="semibold">Source </chakra.span>
<span>{ isOutgoing ? config.chain.name : counterPartyChainNames }</span>
<span>{ isOutgoing ? (baseChain?.app_config ?? config).chain.name : counterPartyChainNames }</span>
</Skeleton>
<Skeleton loading={ isInitialLoading }>
<chakra.span fontWeight="semibold">Destination </chakra.span>
<span>{ isOutgoing ? counterPartyChainNames : config.chain.name }</span>
<span>{ isOutgoing ? counterPartyChainNames : (baseChain?.app_config ?? config).chain.name }</span>
</Skeleton>
<Skeleton loading={ isInitialLoading || isLoading }>
<chakra.span fontWeight="semibold">Txns { isOutgoing ? 'sent' : 'received' } </chakra.span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React from 'react';

import type { Props as ChartWidgetContainerProps } from 'src/features/chain-stats/components/ChartWidgetContainer';
import { getChartUrl } from 'src/features/chain-stats/utils/chart';
import { useMultichainContext } from 'src/features/multichain/context';

import { SankeyChartWidget } from 'src/toolkit/components/charts/sankey/SankeyChartWidget';

Expand All @@ -18,7 +19,8 @@ interface Props extends Omit<ChartWidgetContainerProps, 'id'> {

const ChartWidgetContainerCrossChain = ({ id, interval, isLoading, onLoadingError, className, href, title, description }: Props) => {

const query = useCrossChainChartQuery({ id, interval, enabled: !isLoading });
const chainData = useMultichainContext()?.chain;
const query = useCrossChainChartQuery({ id, interval, enabled: !isLoading, chainData });

React.useEffect(() => {
if (query.isError) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const CrossChainTxsStatusTag = ({ status: statusProp, mode = 'compact', ...rest
return { status: 'error' as const, text: 'Failed' };
case MessageStatus.MESSAGE_STATUS_INITIATED:
return { status: 'pending' as const, text: 'Initiated' };
case MessageStatus.MESSAGE_STATUS_READY_TO_CLAIM:
return { status: 'pending' as const, text: 'Ready to claim' };
default:
return { status: undefined, text: undefined };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const TransactionsCrossChainListItem = ({ data, isLoading, rowGap = 3, currentAd
{ data.transfers.length }
</Link>
) : (
<Skeleton loading={ isLoading } color="text.secondary">{ data.transfers.length }</Skeleton>
<Skeleton loading={ isLoading } color="text.secondary"><span>{ data.transfers.length }</span></Skeleton>
) }
<Skeleton loading={ isLoading }>
Sender
Expand Down
7 changes: 5 additions & 2 deletions src/features/cross-chain-txs/hooks/useCrossChainChartQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { pickBy, uniqBy } from 'es-toolkit';

import type { ChartDataPayloadSankey, StatsIntervalIds } from 'src/features/chain-stats/types/client';
import type { ClusterChainConfig } from 'src/features/multichain/types/client';

import useApiQuery from 'src/api/hooks/useApiQuery';

Expand All @@ -19,17 +20,19 @@ interface Props {
interval: StatsIntervalIds;
enabled?: boolean;
counterPartyChainIds?: Array<string>;
chainData?: ClusterChainConfig;
}

export default function useCrossChainChartQuery({ id, interval, enabled = true, counterPartyChainIds }: Props) {
export default function useCrossChainChartQuery({ id, interval, enabled = true, counterPartyChainIds, chainData }: Props) {

const chart = CROSS_CHAIN_TXS_CHARTS.find((chart) => chart.id === id) ?? CROSS_CHAIN_TXS_CHARTS[0];
const { start: startDate, end: endDate } = getDatesFromInterval(interval);

const resourceName = chart.resourceName;

return useApiQuery<typeof resourceName, unknown, ChartDataPayloadSankey>(resourceName, {
pathParams: { chainId: config.chain.id },
pathParams: { chainId: (chainData?.app_config ?? config).chain.id },
chain: chainData,
queryParams: pickBy({
from: startDate,
to: endDate,
Expand Down
Loading
Loading