Compare commits

..
Author SHA1 Message Date
asiaznik 3506fce0b6 fix: e2e 2024-01-02 16:34:03 +01:00
asiaznik 3bfd13d261 fix: e2e 2024-01-02 15:16:39 +01:00
asiaznik 3bbbcd9be1 fix: e2e 2024-01-02 15:08:54 +01:00
asiaznik 042e5fd23b chore: test ids 2024-01-02 14:35:56 +01:00
asiaznik 13a47f6439 fix: ag grid widths on mobile, stacked cell ellipssis 2024-01-02 14:07:43 +01:00
Matthew Russell b1ea86b081 chore: reduce page padding on referrals to match fees and rewards pages 2024-01-02 11:40:52 +01:00
Dariusz Majcherczyk b943adfe13 chore: revert changes for e2e tests 2024-01-02 11:40:52 +01:00
Dariusz Majcherczyk 16f88e9577 chore: fix e2e tests 2024-01-02 11:40:52 +01:00
asiaznik 2944e578d8 chore: test ids 2024-01-02 11:40:52 +01:00
asiaznik 51ab1005b9 fix: missing ref 2024-01-02 11:40:52 +01:00
asiaznik f6965ce119 chore: pinned row tests 2024-01-02 11:40:52 +01:00
asiaznik 9bffbbd557 chore: fees mobile view 2024-01-02 11:40:52 +01:00
asiaznik ea3af6a234 chore: referrals mobile view 2024-01-02 11:40:52 +01:00
1062 changed files with 23451 additions and 53742 deletions
+1 -2
View File
@@ -196,9 +196,9 @@ jobs:
cypress:
needs: [build-sources, check-e2e-needed]
name: '(CI) cypress'
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
if: needs.check-e2e-needed.outputs.run-tests == 'true' && (contains(needs.build-sources.outputs.projects, 'governance') || contains(needs.build-sources.outputs.projects, 'explorer'))
with:
projects: ${{ needs.build-sources.outputs.projects-e2e }}
tags: '@smoke'
@@ -287,7 +287,6 @@ jobs:
steps:
- run: |
result="${{ needs.cypress.result }}"
echo "Result: $result"
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
+4 -47
View File
@@ -10,7 +10,7 @@ on:
inputs:
console-test-branch:
type: choice
description: 'main: v0.73.13, develop: v0.74.0'
description: 'main: v0.73.5, develop: v0.73.5'
options:
- main
- develop
@@ -19,7 +19,7 @@ jobs:
create-docker-image:
name: Create docker image for console-test
runs-on: ubuntu-22.04
timeout-minutes: 45
timeout-minutes: 20
steps:
#----------------------------------------------
# check-out frontend-monorepo
@@ -138,7 +138,7 @@ jobs:
name: run-tests
runs-on: 8-cores
needs: [create-docker-image, console-test-branch]
timeout-minutes: 45
timeout-minutes: 20
steps:
#----------------------------------------------
# load docker image
@@ -205,7 +205,7 @@ jobs:
# run tests
#----------------------------------------------
- name: Run tests
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v --numprocesses 4 --dist loadfile --durations=45
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
working-directory: apps/trading/e2e
#----------------------------------------------
# upload traces
@@ -227,46 +227,3 @@ jobs:
name: worker-logs
path: ./logs/
retention-days: 15
#----------------------------------------------
# ----- upload market-sim logs -----
#----------------------------------------------
- name: Prepare and Zip market-sim-logs
if: always()
run: |
parent_dir="/tmp/market-sim-logs"
echo "Creating parent directory at $parent_dir"
mkdir -p "$parent_dir"
echo "Waiting for vega-sim-* folders to be created..."
sleep 10 # Waits 10 seconds to ensure all folders are created
echo "Before searching for vega-sim-* folders in /tmp..."
folders=$(find /tmp -mindepth 1 -type d -name 'vega-sim-*' -print) || echo "Find command failed with exit code $?"
echo "After searching for vega-sim-* folders in /tmp..."
if [ -z "$folders" ]; then
echo "No vega-sim-* folders found."
exit 0
fi
echo "Moving vega-sim-* folders to $parent_dir"
echo "$folders" | xargs -I {} mv {} "$parent_dir/"
echo "Checking if $parent_dir is not empty..."
if [ "$(ls -A $parent_dir)" ]; then
echo "Zipping the parent directory..."
zip -r market-sim-logs.zip "$parent_dir" && echo "Zip file created successfully."
else
echo "$parent_dir is empty. No zip file created."
exit 0
fi
shell: /usr/bin/bash -e {0}
- name: Upload market-sim-logs
uses: actions/upload-artifact@v3
if: always()
with:
name: market-sim-logs
path: market-sim-logs.zip
retention-days: 15
+36
View File
@@ -0,0 +1,36 @@
name: Cypress Console tests -- live environment
# This workflow runs using provided url
on:
workflow_dispatch:
inputs:
url:
description: 'Url'
required: true
type: string
jobs:
cypress-run:
name: Run Cypress Trading tests -- live environment
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 20
id: Node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Run Cypress tests
uses: cypress-io/github-action@v4
with:
browser: chrome
record: true
project: ./apps/trading-e2e
config: baseUrl=${{ github.event.inputs.url }}
env: grepTags=@live
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -12,6 +12,7 @@ on:
options:
- explorer-e2e
- governance-e2e
- trading-e2e
tags:
description: 'Test tags to run'
required: true
+1 -1
View File
@@ -10,5 +10,5 @@ jobs:
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: '["explorer-e2e","governance-e2e"]'
projects: '["explorer-e2e","governance-e2e","trading-e2e"]'
tags: '@smoke @regression @slow'
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
- name: Install dependencies
run: |
rm package.json
npm install --no-save @commitlint/cli@16.3.0 @commitlint/config-conventional@18.6.1 @commitlint/config-nx-scopes@18.6.1 nx@17.1.2
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx @commitlint/cli@16.3.0 --config ./commitlint.config-ci.js
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
-28
View File
@@ -1,28 +0,0 @@
# path to a directory with all packages
storage: ../tmp/local-registry/storage
# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.yarnpkg.com
maxage: 60m
packages:
'**':
# give all users (including non-authenticated users) full access
# because it is a local registry
access: $all
publish: $all
unpublish: $all
# if package is not available locally, proxy requests to npm registry
proxy: npmjs
# log settings
logs:
type: stdout
format: pretty
level: warn
publish:
allow_offline: true # set offline to true to allow publish offline
+5 -5
View File
@@ -4,7 +4,7 @@ The front-end monorepo provides a toolkit for building apps that interact with V
This repository is managed using [Nx](https://nx.dev).
## 🔎 Applications in this repo
# 🔎 Applications in this repo
### [Block explorer](./apps/explorer)
@@ -30,7 +30,7 @@ Hosting for static content being shared across apps, for example fonts.
The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract.
## 🧱 Libraries in this repo
# 🧱 Libraries in this repo
### [UI toolkit](./libs/ui-toolkit)
@@ -53,7 +53,7 @@ A utility library for connecting to the Ethereum network and interacting with Ve
Generic react helpers that can be used across multiple applications, along with other utilities.
## 💻 Develop
# 💻 Develop
### Set up
@@ -103,7 +103,7 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work
Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more.
## 🐋 Hosting a console
# 🐋 Hosting a console
To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions).
@@ -226,6 +226,6 @@ Note: The script is only needed if capsule was built for first time or fresh. To
vega wallet service run -n DV --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet
```
## 📑 License
# 📑 License
[MIT](./LICENSE)
@@ -1,7 +1,7 @@
import { getNewAssetTxBody } from '../support/governance.functions';
context('Proposal page', { tags: '@smoke' }, function () {
describe.skip('Verify elements on page', function () {
describe('Verify elements on page', function () {
const proposalHeading = 'proposals-heading';
const dateTimeRegex =
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
@@ -24,6 +24,10 @@ context('Proposal page', { tags: '@smoke' }, function () {
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
cy.get_element_by_col_id('type').should('have.text', 'NewMarket');
cy.get_element_by_col_id('state').should('have.text', 'Enacted');
cy.getByTestId('vote-progress').should('be.visible');
cy.getByTestId('vote-progress-bar-for')
.invoke('attr', 'style')
.should('eq', 'width: 100%;');
cy.get('[col-id="cDate"]')
.invoke('text')
.should('match', dateTimeRegex);
@@ -40,7 +44,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
cy.getByTestId('icon-cross').click();
});
it('Proposal page displayed on mobile', function () {
it.skip('Proposal page displayed on mobile', function () {
const proposalTitle = 'Add Lorem Ipsum market';
cy.common_switch_to_mobile_and_click_toggle();
@@ -51,7 +55,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
});
});
it.skip('Able to view new asset proposal', function () {
it('Able to view new asset proposal', function () {
const proposalTitle = 'Test new asset proposal';
const newAssetProposalBody = getNewAssetTxBody();
cy.VegaWalletSubmitProposal(newAssetProposalBody);
@@ -69,6 +73,10 @@ context('Proposal page', { tags: '@smoke' }, function () {
'have.text',
'Waiting for Node Vote'
);
cy.getByTestId('vote-progress').should('be.visible');
cy.getByTestId('vote-progress-bar-against')
.invoke('attr', 'style')
.should('eq', 'width: 100%;');
cy.get('[col-id="cDate"]')
.invoke('text')
.should('match', dateTimeRegex);
+2
View File
@@ -1,4 +1,5 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.rocks
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1
@@ -8,6 +9,7 @@ NX_VEGA_WALLET_URL=http://localhost:1789
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.rocks/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
+1
View File
@@ -1,5 +1,6 @@
# App configuration variables
NX_VEGA_ENV=CUSTOM
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
+1
View File
@@ -2,6 +2,7 @@
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_ENV=DEVNET
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://dev.governance.vega.xyz
NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
+1
View File
@@ -4,6 +4,7 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/maste
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.vega.community/rest
NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz/
+1
View File
@@ -4,6 +4,7 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-inter
NX_VEGA_URL=https://api.mainnet-mirror.vega.rocks/graphql
NX_VEGA_ENV=MAINNET_MIRROR
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.rocks/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.mainnet-mirror.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks/
+1
View File
@@ -4,6 +4,7 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-inter
NX_VEGA_ENV=TESTNET
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.fairground.wtf
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
+1
View File
@@ -4,6 +4,7 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/maste
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
+1
View File
@@ -3,6 +3,7 @@ NX_TENDERMINT_URL=http://localhost:26607/
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
NX_VEGA_ENV=CUSTOM
NX_BLOCK_EXPLORER=
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
@@ -7,7 +7,6 @@ export type AssetBalanceProps = {
price: string;
showAssetLink?: boolean;
showAssetSymbol?: boolean;
rounded?: boolean;
};
/**
@@ -19,17 +18,12 @@ const AssetBalance = ({
price,
showAssetLink = true,
showAssetSymbol = false,
rounded = false,
}: AssetBalanceProps) => {
const { data: asset, loading } = useAssetDataProvider(assetId);
const label =
!loading && asset && asset.decimals
? addDecimalsFixedFormatNumber(
price,
asset.decimals,
rounded ? 0 : undefined
)
? addDecimalsFixedFormatNumber(price, asset.decimals)
: price;
return (
@@ -41,7 +41,6 @@ export const Header = () => {
Routes.ASSETS,
Routes.MARKETS,
Routes.GOVERNANCE,
Routes.TREASURY,
Routes.NETWORK_PARAMETERS,
Routes.GENESIS,
].map((n) => pages.find((r) => r.path === n))
@@ -0,0 +1,34 @@
import React from 'react';
import { DATA_SOURCES } from '../../../config';
import Hash from '../hash';
export enum EthExplorerLinkTypes {
block = 'block',
address = 'address',
tx = 'tx',
}
export type EthExplorerLinkProps = Partial<typeof HTMLAnchorElement> & {
id: string;
type: EthExplorerLinkTypes;
};
export const EthExplorerLink = ({
id,
type,
...props
}: EthExplorerLinkProps) => {
const link = `${DATA_SOURCES.ethExplorerUrl}/${type}/${id}`;
return (
<a
className="underline external font-mono"
target="_blank"
rel="noopener noreferrer"
{...props}
href={link}
>
<Hash text={id} />
</a>
);
};
@@ -1,34 +0,0 @@
import type { ChainIdMapping } from './external-chain';
import { SUPPORTED_CHAIN_IDS, SUPPORTED_CHAIN_LABELS } from './external-chain';
export const SUPPORTED_CHAIN_ICON_URLS: ChainIdMapping = {
'1': '/assets/chain-eth-logo.svg',
'100': '/assets/chain-gno-logo.svg',
'42161': '/assets/chain-arb-logo.svg',
'11155111': '/assets/chain-eth-logo.svg',
};
export type ExternalChainIconProps = {
chainId?: string;
};
export const ExternalChainIcon = ({
// If chainID is not provided, default to a non-existent chain
chainId = '-1',
}: ExternalChainIconProps) => {
if (SUPPORTED_CHAIN_IDS.includes(chainId)) {
const url = SUPPORTED_CHAIN_ICON_URLS[chainId];
const alt = SUPPORTED_CHAIN_LABELS[chainId];
return (
<img
src={url}
className="inline-block w-4 h-4 mr-1 dark:invert"
alt={alt}
title={alt}
/>
);
} else {
return null;
}
};
@@ -1,38 +0,0 @@
export type ChainIdMapping = {
[K in typeof SUPPORTED_CHAIN_IDS[number]]: string;
};
export const SUPPORTED_CHAIN_IDS: string[] = ['1', '100', '42161', '11155111'];
export const SUPPORTED_CHAIN_LABELS: ChainIdMapping = {
'1': 'Ethereum',
'100': 'Gnosis',
'42161': 'Arbitrum',
'11155111': 'Sepolia',
};
export function getExternalExplorerLink(chainId: string, type: string) {
if (SUPPORTED_CHAIN_IDS.includes(chainId)) {
switch (chainId) {
case '1':
return 'https://etherscan.io';
case '100':
return 'https://gnosisscan.io';
case '42161':
return 'https://arbiscan.io';
case '11155111':
return 'https://sepolia.etherscan.io';
default:
return '#';
}
} else {
return '#';
}
}
export function getExternalChainLabel(chainId?: string) {
if (chainId && SUPPORTED_CHAIN_IDS.includes(chainId)) {
return SUPPORTED_CHAIN_LABELS[chainId];
} else {
return 'Custom Chain';
}
}
@@ -1,41 +0,0 @@
import Hash from '../hash';
import { getExternalExplorerLink } from './external-chain';
import { ExternalChainIcon } from './external-chain-icon';
export enum EthExplorerLinkTypes {
block = 'block',
address = 'address',
tx = 'tx',
}
export type ExternalExplorerLinkProps = Partial<typeof HTMLAnchorElement> & {
id: string;
type: EthExplorerLinkTypes;
// Defaults to Ethereum Mainnet, as chain support was added late
chain?: string;
code?: boolean;
};
export const ExternalExplorerLink = ({
id,
type,
chain = '1',
code = false,
...props
}: ExternalExplorerLinkProps) => {
const link = `${getExternalExplorerLink(chain, type)}/${type}/${id}${
code ? '#code' : ''
}`;
return (
<a
className="underline external font-mono"
target="_blank"
rel="noopener noreferrer"
{...props}
href={link}
>
<ExternalChainIcon chainId={chain} />
<Hash text={id} />
</a>
);
};
@@ -1,4 +1,4 @@
export type HashProps = React.HTMLProps<HTMLSpanElement> & {
export type HashProps = {
text: string;
truncate?: boolean;
};
@@ -2,5 +2,4 @@ export { default as BlockLink } from './block-link/block-link';
export { default as PartyLink } from './party-link/party-link';
export { default as NodeLink } from './node-link/node-link';
export { default as MarketLink } from './market-link/market-link';
export { default as NetworkParameterLink } from './network-parameter-link/network-parameter-link';
export * from './asset-link/asset-link';
@@ -1,30 +0,0 @@
import React from 'react';
import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type NetworkParameterLinkProps = Partial<ComponentProps<typeof Link>> & {
parameter: string;
};
/**
* Links a given network parameter to the relevant page and anchor on the page
*/
const NetworkParameterLink = ({
parameter,
...props
}: NetworkParameterLinkProps) => {
return (
<Link
className="underline"
{...props}
to={`/${Routes.NETWORK_PARAMETERS}#${parameter}`}
>
<Hash text={parameter} />
</Link>
);
};
export default NetworkParameterLink;
@@ -32,17 +32,9 @@ export function getNameForParty(id: string, data?: ExplorerNodeNamesQuery) {
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
truncate?: boolean;
networkLabel?: string;
truncateLength?: number;
};
const PartyLink = ({
id,
truncate = false,
truncateLength = 4,
networkLabel = t('Network'),
...props
}: PartyLinkProps) => {
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
const { data } = useExplorerNodeNamesQuery();
const name = useMemo(() => getNameForParty(id, data), [data, id]);
const useName = name !== id;
@@ -52,7 +44,7 @@ const PartyLink = ({
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
return (
<span className="font-mono" data-testid="network">
{networkLabel}
{t('Network')}
</span>
);
}
@@ -68,7 +60,7 @@ const PartyLink = ({
}
return (
<span>
<span className="whitespace-nowrap">
{useName && <Icon size={4} name="cube" className="mr-2" />}
<Link
className="underline font-mono"
@@ -78,11 +70,7 @@ const PartyLink = ({
{useName ? (
name
) : (
<Hash
text={
truncate ? truncateMiddle(id, truncateLength, truncateLength) : id
}
/>
<Hash text={truncate ? truncateMiddle(id, 4, 4) : id} />
)}
</Link>
</span>
@@ -1,11 +1,9 @@
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
... on Proposal {
id
rationale {
title
description
}
id
rationale {
title
description
}
}
}
@@ -8,18 +8,16 @@ export type ExplorerProposalQueryVariables = Types.Exact<{
}>;
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
export const ExplorerProposalDocument = gql`
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
... on Proposal {
id
rationale {
title
description
}
id
rationale {
title
description
}
}
}
@@ -3,11 +3,7 @@ import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import ProposalLink from './proposal-link';
import {
ExplorerProposalDocument,
type ExplorerProposalQuery,
type ExplorerProposalQueryVariables,
} from './__generated__/Proposal';
import { ExplorerProposalDocument } from './__generated__/Proposal';
import { GraphQLError } from 'graphql';
function renderComponent(id: string, mocks: MockedResponse[]) {
@@ -27,10 +23,7 @@ describe('Proposal link component', () => {
});
it('Renders the ID on error', async () => {
const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
@@ -47,22 +40,17 @@ describe('Proposal link component', () => {
});
it('Renders the proposal title when the query returns a result', async () => {
const proposalId = '123';
const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
id: proposalId,
id: '123',
},
},
result: {
data: {
proposal: {
__typename: 'Proposal',
id: proposalId,
id: '123',
rationale: {
title: 'test-title',
description: 'test description',
@@ -72,16 +60,13 @@ describe('Proposal link component', () => {
},
};
const res = render(renderComponent(proposalId, [mock]));
expect(res.getByText(proposalId)).toBeInTheDocument();
const res = render(renderComponent('123', [mock]));
expect(res.getByText('123')).toBeInTheDocument();
expect(await res.findByText('test-title')).toBeInTheDocument();
});
it('Leaves the proposal id when the market is not found', async () => {
const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
@@ -1,11 +1,7 @@
import {
useExplorerProposalQuery,
type ExplorerProposalQuery,
} from './__generated__/Proposal';
import { useExplorerProposalQuery } from './__generated__/Proposal';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { ENV } from '../../../config/env';
import Hash from '../hash';
export type ProposalLinkProps = {
id: string;
text?: string;
@@ -20,13 +16,8 @@ const ProposalLink = ({ id, text }: ProposalLinkProps) => {
variables: { id },
});
const proposal = data?.proposal as Extract<
ExplorerProposalQuery['proposal'],
{ __typename?: 'Proposal' }
>;
const base = ENV.dataSources.governanceUrl;
const label = proposal?.rationale?.title || id;
const label = data?.proposal?.rationale.title || id;
return (
<ExternalLink href={`${base}/proposals/${id}`}>
@@ -1,7 +1,6 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import {
LiquidationStrategyInfoPanel,
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MarginScalingFactorsPanel,
@@ -95,8 +94,6 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
</>
)
)}
<h2 className={headerClassName}>{t('Liquidation strategy')}</h2>
<LiquidationStrategyInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
@@ -1,5 +1,9 @@
import { render, waitFor } from '@testing-library/react';
import { NestedDataList, sortNestedDataByChildren } from './nested-data-list';
import {
BORDER_COLOURS,
NestedDataList,
sortNestedDataByChildren,
} from './nested-data-list';
import userEvent from '@testing-library/user-event';
const mockData = {
@@ -57,6 +61,38 @@ describe('NestedDataList', () => {
expect(parent[0].querySelector('li')).toHaveClass('pl-4 border-l-4 pt-2');
});
it('should repeat the border colours in the correct order', () => {
const colourMockData = {
t0: {
t1: {
t2: {
t3: {
t4: {
t5: {
t6: {
t7: {
t8: {
hello: 'world',
},
},
},
},
},
},
},
},
},
};
const tree = render(<NestedDataList data={colourMockData} />);
const { getByTestId } = tree;
for (let i = 0; i < 8; i++) {
const item = getByTestId(`T${i}`);
const expected = BORDER_COLOURS.light[i % 5];
expect(item.style.borderColor.toUpperCase()).toBe(expected);
}
});
it('should sort the data by values with children', () => {
const mockData = {
nonce: '5980890939790185837',
@@ -175,7 +175,6 @@ describe('Amend order details', () => {
const res = renderExistingAmend('123', 1, amend);
expect(await res.findByText('New size')).toBeInTheDocument();
expect(await res.findByText('Size ±')).toBeInTheDocument();
});
it('Renders Reference if provided', async () => {
@@ -82,7 +82,7 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
{amend.sizeDelta && amend.sizeDelta !== '0' ? (
<div className="mb-12 md:mb-0">
<h2 className="text-dark mb-4 text-2xl font-bold">
{t('Size ±')}
{t('New size')}
</h2>
<h5
className={`mb-0 text-lg font-medium capitalize text-gray-500 ${getSideDeltaColour(
@@ -93,16 +93,6 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
</h5>
</div>
) : null}
{o && (
<div className="">
<h2 className="text-dark mb-4 text-2xl font-bold">
{t('New size')}
</h2>
<h5 className="mb-0 text-lg font-medium text-gray-500">
{o ? o.size : null}
</h5>
</div>
)}
{amend.price && amend.price !== '0' ? (
<div className="">
@@ -60,7 +60,7 @@ const DeterministicOrderDetails = ({
const o = data.orderByID;
return (
<div className={wrapperClasses}>
<div className="mb-0">
<div className="mb-12 lg:mb-0">
<div className="relative block px-3 py-6 md:px-6 lg:-mr-7">
<h2 className="text-3xl font-bold mb-4 display-5">
<abbr title={tifFull[o.timeInForce]} className="bb-dotted mr-2">
@@ -89,9 +89,9 @@ const DeterministicOrderDetails = ({
<span>{t('Reference')}</span>: {o.reference}
</p>
) : null}
<div className="grid grid-cols-2 md:grid-cols-5 gap-x-6 mt-4">
<div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
<div className="grid md:grid-cols-5 gap-x-6 mt-4">
<div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('Status')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
@@ -99,17 +99,15 @@ const DeterministicOrderDetails = ({
</h5>
</div>
<div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
{t('Size')}
</h2>
<div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
<SizeInMarket size={o.size} marketId={o.market.id} />
</h5>
</div>
<div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
<div className="">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('Version')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
@@ -117,8 +115,8 @@ const DeterministicOrderDetails = ({
</h5>
</div>
{o.type ? (
<div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
<div className="">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('Type')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
@@ -1,4 +1,5 @@
import { type ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals';
import { type AgGridReact } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid';
@@ -11,7 +12,12 @@ import { type ColDef } from 'ag-grid-community';
import type { RowClickedEvent } from 'ag-grid-community';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { ProposalStateMapping } from '@vegaprotocol/types';
import BigNumber from 'bignumber.js';
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
import { BREAKPOINT_MD } from '../../config/breakpoints';
import { JsonViewerDialog } from '../dialogs/json-viewer-dialog';
@@ -25,7 +31,15 @@ type ProposalsTableProps = {
data: ProposalListFieldsFragment[] | null;
};
export const ProposalsTable = ({ data }: ProposalsTableProps) => {
const { params } = useNetworkParams([
NetworkParams.governance_proposal_market_requiredMajority,
]);
const tokenLink = useLinks(DApp.Governance);
const requiredMajorityPercentage = useMemo(() => {
const requiredMajority =
params?.governance_proposal_market_requiredMajority ?? 1;
return new BigNumber(requiredMajority).times(100);
}, [params?.governance_proposal_market_requiredMajority]);
const gridRef = useRef<AgGridReact>(null);
useLayoutEffect(() => {
@@ -76,6 +90,33 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
return value ? ProposalStateMapping[value] : '-';
},
},
{
colId: 'voting',
maxWidth: 100,
hide: window.innerWidth <= BREAKPOINT_MD,
headerName: t('Voting'),
cellRenderer: ({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
if (data) {
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
const noTokens = new BigNumber(data.votes.no.totalTokens);
const totalTokensVoted = yesTokens.plus(noTokens);
const yesPercentage = totalTokensVoted.isZero()
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return (
<div className="flex h-full items-center justify-center pt-2 uppercase">
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
/>
</div>
);
}
return '-';
},
},
{
colId: 'cDate',
maxWidth: 150,
@@ -143,7 +184,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
},
},
],
[tokenLink]
[requiredMajorityPercentage, tokenLink]
);
return (
<>
@@ -30,12 +30,12 @@ export const Signature = ({ signature }: SignatureProps) => {
return (
<div className="inline-flex border rounded signature-component relative pr-[20px]">
<div
<span
className="bg-gray-100 px-2.5 py-0.5 text-xs text-gray-500 select-none cursor-default"
title={`${signature.algo}`}
title={`Version ${signature.version}`}
>
<span>v{signature.version}</span>
</div>
{signature.algo}
</span>
<div
className={
isOpen
@@ -1,10 +1,9 @@
import { TableCell, TableRow } from '../../../table';
import { t } from '@vegaprotocol/i18n';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/external-explorer-link/external-explorer-link';
import { getExternalChainLabel } from '../../../links/external-explorer-link/external-chain';
} from '../../../links/eth-explorer-link/eth-explorer-link';
import type { components } from '../../../../../types/explorer';
import { defaultAbiCoder, base64 } from 'ethers/lib/utils';
import { BigNumber } from 'ethers';
@@ -45,10 +44,10 @@ export const TxDetailsContractCall = ({
},
});
if (!contractCall) {
if (!contractCall || !contractCall.result) {
return null;
}
const chainLabel = getExternalChainLabel(contractCall.sourceChainId);
return (
<>
{contractCall.specId && (
@@ -65,12 +64,9 @@ export const TxDetailsContractCall = ({
)}
{contractCall.blockHeight && (
<TableRow modifier="bordered">
<TableCell>{t('ETH block')}</TableCell>
<TableCell>
{chainLabel} {t('block')}
</TableCell>
<TableCell>
<ExternalExplorerLink
chain={contractCall.sourceChainId}
<EthExplorerLink
id={contractCall.blockHeight}
type={EthExplorerLinkTypes.block}
/>
@@ -79,24 +75,14 @@ export const TxDetailsContractCall = ({
)}
{data?.oracleSpec?.dataSourceSpec && (
<OracleEthSource
chain={contractCall.sourceChainId}
sourceType={data.oracleSpec.dataSourceSpec.spec.data.sourceType}
/>
)}
{contractCall.error && (
<TableRow modifier="bordered">
<TableCell>{t('Call error')}</TableCell>
<TableCell>{contractCall.error}</TableCell>
</TableRow>
)}
{contractCall.result && (
<TableRow modifier="bordered">
<TableCell>{t('Result')}</TableCell>
<TableCell>{decodeEthCallResult(contractCall.result)}</TableCell>
</TableRow>
)}
<TableRow modifier="bordered">
<TableCell>{t('Result')}</TableCell>
<TableCell>{decodeEthCallResult(contractCall.result)}</TableCell>
</TableRow>
</>
);
};
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/external-explorer-link/external-explorer-link';
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventErc20AssetLimitsUpdatedProps {
assetLimitsUpdated: components['schemas']['vegaERC20AssetLimitsUpdated'];
@@ -42,7 +42,7 @@ export const TxDetailsChainEventErc20AssetLimitsUpdated = ({
<TableRow modifier="bordered">
<TableCell>{t('ERC20 asset')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
id={assetLimitsUpdated.sourceEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/external-explorer-link/external-explorer-link';
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventErc20AssetListProps {
assetList: components['schemas']['vegaERC20AssetList'];
@@ -32,7 +32,7 @@ export const TxDetailsChainEventErc20AssetList = ({
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
id={assetList.assetSource}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink, PartyLink } from '../../../links';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/external-explorer-link/external-explorer-link';
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventProps {
deposit: components['schemas']['vegaERC20Deposit'];
@@ -36,7 +36,7 @@ export const TxDetailsChainEventDeposit = ({
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
id={deposit.sourceEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/external-explorer-link/external-explorer-link';
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventWithdrawalProps {
withdrawal: components['schemas']['vegaERC20Withdrawal'];
@@ -35,7 +35,7 @@ export const TxDetailsChainEventWithdrawal = ({
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
id={withdrawal.targetEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { PartyLink } from '../../../links';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/external-explorer-link/external-explorer-link';
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventStakeDepositProps {
deposit: components['schemas']['vegaStakeDeposited'];
@@ -39,7 +39,7 @@ export const TxDetailsChainEventStakeDeposit = ({
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
id={deposit.ethereumAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { PartyLink } from '../../../links';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/external-explorer-link/external-explorer-link';
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventStakeRemoveProps {
remove: components['schemas']['vegaStakeRemoved'];
@@ -39,7 +39,7 @@ export const TxDetailsChainEventStakeRemove = ({
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
id={remove.ethereumAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { t } from '@vegaprotocol/i18n';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/external-explorer-link/external-explorer-link';
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventStakeTotalSupplyProps {
update: components['schemas']['vegaStakeTotalSupply'];
@@ -38,7 +38,7 @@ export const TxDetailsChainEventStakeTotalSupply = ({
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
id={update.tokenAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -1,14 +1,7 @@
query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) {
... on Proposal {
id
state
rejectionReason
}
... on BatchProposal {
id
state
rejectionReason
}
id
state
rejectionReason
}
}
@@ -8,22 +8,15 @@ export type ExplorerProposalStatusQueryVariables = Types.Exact<{
}>;
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export const ExplorerProposalStatusDocument = gql`
query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) {
... on Proposal {
id
state
rejectionReason
}
... on BatchProposal {
id
state
rejectionReason
}
id
state
rejectionReason
}
}
`;
@@ -1,257 +0,0 @@
import { render, screen } from '@testing-library/react';
import { BatchItem } from './batch-item';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { components } from '../../../../../types/explorer';
type Item = components['schemas']['vegaBatchProposalTermsChange'];
describe('BatchItem', () => {
it('Renders "Unknown proposal type" by default', () => {
const item = {};
render(<BatchItem item={item} />);
expect(screen.getByText('Unknown proposal type')).toBeInTheDocument();
});
it('Renders "Unknown proposal type" for unknown items', () => {
const item = {
newLochNessMonster: {
location: 'Loch Ness',
},
} as unknown as Item;
render(<BatchItem item={item} />);
expect(screen.getByText('Unknown proposal type')).toBeInTheDocument();
});
it('Renders "New spot market"', () => {
const item = {
newSpotMarket: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New spot market')).toBeInTheDocument();
});
it('Renders "Cancel transfer"', () => {
const item = {
cancelTransfer: {
changes: {
transferId: 'transfer123',
},
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Cancel transfer')).toBeInTheDocument();
expect(screen.getByText('transf')).toBeInTheDocument();
});
it('Renders "Cancel transfer" without an id', () => {
const item = {
cancelTransfer: {
changes: {},
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Cancel transfer')).toBeInTheDocument();
});
it('Renders "New freeform"', () => {
const item = {
newFreeform: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New freeform proposal')).toBeInTheDocument();
});
it('Renders "New market"', () => {
const item = {
newMarket: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New market')).toBeInTheDocument();
});
it('Renders "New transfer"', () => {
const item = {
newTransfer: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New transfer')).toBeInTheDocument();
});
it('Renders "Update asset" with assetId', () => {
const item = {
updateAsset: {
assetId: 'asset123',
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update asset')).toBeInTheDocument();
expect(screen.getByText('asset123')).toBeInTheDocument();
});
it('Renders "Update asset" even if assetId is not set', () => {
const item = {
updateAsset: {
assetId: undefined,
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Update asset')).toBeInTheDocument();
});
it('Renders "Update market state" with marketId', () => {
const item = {
updateMarketState: {
changes: {
marketId: 'market123',
},
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market state')).toBeInTheDocument();
expect(screen.getByText('market123')).toBeInTheDocument();
});
it('Renders "Update market state" even if marketId is not set', () => {
const item = {
updateMarketState: {
changes: {
marketId: undefined,
},
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market state')).toBeInTheDocument();
});
it('Renders "Update network parameter" with parameter', () => {
const item = {
updateNetworkParameter: {
changes: {
key: 'parameter123',
},
},
};
render(
<MockedProvider>
<MemoryRouter>
<BatchItem item={item} />
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText('Update network parameter')).toBeInTheDocument();
expect(screen.getByText('parameter123')).toBeInTheDocument();
});
it('Renders "Update network parameter" even if parameter is not set', () => {
const item = {
updateNetworkParameter: {
changes: {
key: undefined,
},
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Update network parameter')).toBeInTheDocument();
});
it('Renders "Update referral program"', () => {
const item = {
updateReferralProgram: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Update referral program')).toBeInTheDocument();
});
it('Renders "Update spot market" with marketId', () => {
const item = {
updateSpotMarket: {
marketId: 'market123',
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update spot market')).toBeInTheDocument();
expect(screen.getByText('market123')).toBeInTheDocument();
});
it('Renders "Update spot market" even if marketId is not set', () => {
const item = {
updateSpotMarket: {
marketId: undefined,
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update spot market')).toBeInTheDocument();
});
it('Renders "Update market" with marketId', () => {
const item = {
updateMarket: {
marketId: 'market123',
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market')).toBeInTheDocument();
expect(screen.getByText('market123')).toBeInTheDocument();
});
it('Renders "Update market" even if marketId is not set', () => {
const item = {
updateMarket: {
marketId: undefined,
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market')).toBeInTheDocument();
});
it('Renders "Update volume discount program"', () => {
const item = {
updateVolumeDiscountProgram: {},
};
render(<BatchItem item={item} />);
expect(
screen.getByText('Update volume discount program')
).toBeInTheDocument();
});
});
@@ -1,87 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { AssetLink, MarketLink, NetworkParameterLink } from '../../../links';
import type { components } from '../../../../../types/explorer';
import Hash from '../../../links/hash';
type Item = components['schemas']['vegaBatchProposalTermsChange'];
export interface BatchItemProps {
item: Item;
}
/**
* Produces a one line summary for an item in a batch proposal. Could
* easily be adapted to summarise individual proposals, but there is no
* place for that yet.
*
* Details (like IDs) should be shown and linked if available, but handled
* if not available. This is adequate as the ProposalSummary component contains
* a JSON viewer for the full proposal.
*/
export const BatchItem = ({ item }: BatchItemProps) => {
if (item.cancelTransfer) {
const transferId = item?.cancelTransfer?.changes?.transferId || false;
return (
<span>
{t('Cancel transfer')}&nbsp;
{transferId && (
<Hash className="ml-1" truncate={true} text={transferId} />
)}
</span>
);
} else if (item.newFreeform) {
return <span>{t('New freeform proposal')}</span>;
} else if (item.newMarket) {
return <span>{t('New market')}</span>;
} else if (item.newSpotMarket) {
return <span>{t('New spot market')}</span>;
} else if (item.newTransfer) {
return <span>{t('New transfer')}</span>;
} else if (item.updateAsset) {
const assetId = item?.updateAsset?.assetId || false;
return (
<span>
{t('Update asset')}
{assetId && <AssetLink className="ml-1" assetId={assetId} />}
</span>
);
} else if (item.updateMarket) {
const marketId = item?.updateMarket?.marketId || false;
return (
<span>
{t('Update market')}{' '}
{marketId && <MarketLink className="ml-1" id={marketId} />}
</span>
);
} else if (item.updateMarketState) {
const marketId = item?.updateMarketState?.changes?.marketId || false;
return (
<span>
{t('Update market state')}
{marketId && <MarketLink className="ml-1" id={marketId} />}
</span>
);
} else if (item.updateNetworkParameter) {
const param = item?.updateNetworkParameter?.changes?.key || false;
return (
<span>
{t('Update network parameter')}
{param && <NetworkParameterLink className="ml-1" parameter={param} />}
</span>
);
} else if (item.updateReferralProgram) {
return <span>{t('Update referral program')}</span>;
} else if (item.updateSpotMarket) {
const marketId = item?.updateSpotMarket?.marketId || '';
return (
<span>
{t('Update spot market')}
<MarketLink className="ml-1" id={marketId} />
</span>
);
} else if (item.updateVolumeDiscountProgram) {
return <span>{t('Update volume discount program')}</span>;
}
return <span>{t('Unknown proposal type')}</span>;
};
@@ -14,18 +14,16 @@ export function format(date: string | undefined, def: string) {
return new Date().toLocaleDateString() || def;
}
type Proposal = Extract<
ExplorerProposalStatusQuery['proposal'],
{ __typename?: 'Proposal' }
>;
export function getDate(proposal: Proposal | undefined, terms: Terms): string {
export function getDate(
data: ExplorerProposalStatusQuery | undefined,
terms: Terms
): string {
const DEFAULT = t('Unknown');
if (!proposal?.state) {
if (!data?.proposal?.state) {
return DEFAULT;
}
switch (proposal.state) {
switch (data.proposal.state) {
case 'STATE_DECLINED':
return `${t('Rejected on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
case 'STATE_ENACTED':
@@ -64,11 +62,9 @@ export const ProposalDate = ({ terms, id }: ProposalDateProps) => {
},
});
const proposal = data?.proposal as Proposal;
return (
<Lozenge className="font-sans text-xs float-right">
{getDate(proposal, terms)}
{getDate(data, terms)}
</Lozenge>
);
};
@@ -2,8 +2,17 @@ import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import type { IconProps } from '@vegaprotocol/ui-toolkit';
import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
import type * as Apollo from '@apollo/client';
import type * as Types from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
type ProposalQueryResult = Apollo.QueryResult<
ExplorerProposalStatusQuery,
Types.Exact<{
id: string;
}>
>;
interface ProposalStatusIconProps {
id: string;
}
@@ -20,38 +29,29 @@ type IconAndLabel = {
* @param data a data result from useExplorerProposalStatusQuery
* @returns Icon name
*/
export function useIconAndLabelForStatus(id: string): IconAndLabel {
const { data, loading, error } = useExplorerProposalStatusQuery({
variables: {
id,
},
});
const proposal = data?.proposal as Extract<
ExplorerProposalStatusQuery['proposal'],
{ __typename?: 'Proposal' }
>;
export function getIconAndLabelForStatus(
res: ProposalQueryResult
): IconAndLabel {
const DEFAULT: IconAndLabel = {
icon: 'error',
label: t('Proposal state unknown'),
};
if (loading) {
if (res.loading) {
return {
icon: 'more',
label: t('Loading data'),
};
}
if (!data?.proposal || error) {
if (!res?.data?.proposal || res.error) {
return {
icon: 'error',
label: error?.message || DEFAULT.label,
label: res.error?.message || DEFAULT.label,
};
}
switch (proposal.state) {
switch (res.data.proposal.state) {
case 'STATE_DECLINED':
return {
icon: 'stop',
@@ -99,7 +99,13 @@ export function useIconAndLabelForStatus(id: string): IconAndLabel {
/**
*/
export const ProposalStatusIcon = ({ id }: ProposalStatusIconProps) => {
const { icon, label } = useIconAndLabelForStatus(id);
const { icon, label } = getIconAndLabelForStatus(
useExplorerProposalStatusQuery({
variables: {
id,
},
})
);
return (
<div className="float-left mr-3">
@@ -1,4 +1,6 @@
import type { ProposalTerms } from '../tx-proposal';
import { useState } from 'react';
import type { components } from '../../../../../types/explorer';
import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog';
import ProposalLink from '../../../links/proposal-link/proposal-link';
import truncate from 'lodash/truncate';
@@ -7,12 +9,7 @@ import ReactMarkdown from 'react-markdown';
import { ProposalDate } from './proposal-date';
import { t } from '@vegaprotocol/i18n';
import type { ProposalTerms } from '../tx-proposal';
import type { components } from '../../../../../types/explorer';
import { BatchItem } from './batch-item';
type Rationale = components['schemas']['vegaProposalRationale'];
type Batch = components['schemas']['v1BatchProposalSubmissionTerms']['changes'];
type ProposalTermsDialog = {
open: boolean;
@@ -24,7 +21,6 @@ interface ProposalSummaryProps {
id: string;
rationale?: Rationale;
terms?: ProposalTerms;
batch?: Batch;
}
/**
@@ -35,7 +31,6 @@ export const ProposalSummary = ({
id,
rationale,
terms,
batch,
}: ProposalSummaryProps) => {
const [dialog, setDialog] = useState<ProposalTermsDialog>({
open: false,
@@ -64,9 +59,7 @@ export const ProposalSummary = ({
return (
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5">
{id && <ProposalStatusIcon id={id} />}
{rationale?.title && (
<h1 className="text-xl pb-1 break-all">{rationale.title}</h1>
)}
{rationale?.title && <h1 className="text-xl pb-1">{rationale.title}</h1>}
{rationale?.description && (
<div className="pt-2 text-sm leading-tight">
<ReactMarkdown
@@ -79,18 +72,6 @@ export const ProposalSummary = ({
</ReactMarkdown>
</div>
)}
{batch && (
<section className="pt-2 text-sm leading-tight my-3">
<h2 className="text-lg pb-1">{t('Changes')}</h2>
<ol>
{batch.map((change, index) => (
<li className="ml-4 list-decimal" key={`batch-${index}`}>
<BatchItem item={change} />
</li>
))}
</ol>
</section>
)}
<div className="pt-5">
<button className="underline max-md:hidden mr-5" onClick={openDialog}>
{t('View terms')}
@@ -1,46 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { TxDetailsShared } from '../shared/tx-details-shared';
import { TableWithTbody } from '../../../table';
import type { components } from '../../../../../types/explorer';
import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response';
import { TableCell, TableRow } from '../../../table';
type Update = components['schemas']['v1UpdatePartyProfile'];
interface TxDetailsUpdatePartyProfileProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* Party profiles can be an alias and arbitrary key/values pairs.
* This component displays the alias, if any, but not the metadata. When there is
* some wider usage, we can decide how to render it. For now, it's available in the
* full TX details.
*/
export const TxDetailsUpdatePartyProfile = ({
txData,
pubKey,
blockData,
}: TxDetailsUpdatePartyProfileProps) => {
if (!txData?.command.updatePartyProfile) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const update: Update = txData.command.updatePartyProfile;
return (
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{update.alias && (
<TableRow modifier="bordered">
<TableCell>{t('New alias')}</TableCell>
<TableCell>{update.alias}</TableCell>
</TableRow>
)}
</TableWithTbody>
);
};
@@ -1,54 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerReferralCodeOwnerQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerReferralCodeOwnerQuery = { __typename?: 'Query', referralSets: { __typename?: 'ReferralSetConnection', edges: Array<{ __typename?: 'ReferralSetEdge', node: { __typename?: 'ReferralSet', createdAt: any, updatedAt: any, referrer: string } } | null> } };
export const ExplorerReferralCodeOwnerDocument = gql`
query ExplorerReferralCodeOwner($id: ID!) {
referralSets(id: $id) {
edges {
node {
createdAt
updatedAt
referrer
}
}
}
}
`;
/**
* __useExplorerReferralCodeOwnerQuery__
*
* To run a query within a React component, call `useExplorerReferralCodeOwnerQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerReferralCodeOwnerQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerReferralCodeOwnerQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerReferralCodeOwnerQuery(baseOptions: Apollo.QueryHookOptions<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>(ExplorerReferralCodeOwnerDocument, options);
}
export function useExplorerReferralCodeOwnerLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>(ExplorerReferralCodeOwnerDocument, options);
}
export type ExplorerReferralCodeOwnerQueryHookResult = ReturnType<typeof useExplorerReferralCodeOwnerQuery>;
export type ExplorerReferralCodeOwnerLazyQueryHookResult = ReturnType<typeof useExplorerReferralCodeOwnerLazyQuery>;
export type ExplorerReferralCodeOwnerQueryResult = Apollo.QueryResult<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>;
@@ -1,11 +0,0 @@
query ExplorerReferralCodeOwner($id: ID!) {
referralSets(id: $id) {
edges {
node {
createdAt
updatedAt
referrer
}
}
}
}
@@ -1,95 +0,0 @@
import { render, waitFor } from '@testing-library/react';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { ReferralCodeOwner } from './referral-code-owner';
import type { ReferralCodeOwnerProps } from './referral-code-owner';
import { ExplorerReferralCodeOwnerDocument } from './__generated__/code-owner';
const renderComponent = (
props: ReferralCodeOwnerProps,
mocks: MockedResponse[]
) => {
return render(
<MockedProvider mocks={mocks}>
<MemoryRouter>
<ReferralCodeOwner {...props} />
</MemoryRouter>
</MockedProvider>
);
};
describe('ReferralCodeOwner', () => {
it('should render loading state', () => {
const mocks = [
{
request: {
query: ExplorerReferralCodeOwnerDocument,
variables: {
id: 'ABC123',
},
},
},
];
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
expect(getByText('Loading...')).toBeInTheDocument();
});
it('should render error state', async () => {
const errorMessage = 'Error fetching referrer: ABC123';
const mocks = [
{
request: {
query: ExplorerReferralCodeOwnerDocument,
variables: {
id: 'ABC123',
},
},
error: new Error('nope'),
},
];
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
await waitFor(() => {
expect(getByText(errorMessage)).toBeInTheDocument();
});
});
it('should render link to referring party', async () => {
const referrerId = 'DEF789';
const mocks = [
{
request: {
query: ExplorerReferralCodeOwnerDocument,
variables: {
id: 'ABC123',
},
},
result: {
data: {
referralSets: {
edges: [
{
node: {
__typename: 'ReferralSet',
referrer: referrerId,
createdAt: '123',
updatedAt: '456',
},
},
],
},
},
},
},
];
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
await waitFor(() => {
expect(getByText(referrerId)).toBeInTheDocument();
});
});
});
@@ -1,26 +0,0 @@
import { TableCell } from '../../../table';
import { useExplorerReferralCodeOwnerQuery } from './__generated__/code-owner';
import { PartyLink } from '../../../links';
export interface ReferralCodeOwnerProps {
code: string;
}
/**
* Render the owner of a referral code
*/
export const ReferralCodeOwner = ({ code }: ReferralCodeOwnerProps) => {
const { data, error, loading } = useExplorerReferralCodeOwnerQuery({
variables: {
id: code,
},
});
const referrer = data?.referralSets.edges[0]?.node.referrer || '';
return (
<TableCell>
{loading && 'Loading...'}
{error && `Error fetching referrer: ${code}`}
{referrer.length > 0 && <PartyLink id={referrer} />}
</TableCell>
);
};
@@ -1,93 +0,0 @@
import { render } from '@testing-library/react';
import { ReferralTeam } from './team';
import type { CreateReferralSet } from './team';
import { MockedProvider } from '@apollo/client/testing';
describe('ReferralTeam', () => {
const team = {
name: 'Test Team',
teamUrl: 'https://example.com/team',
avatarUrl: 'https://example.com/avatar',
closed: false,
};
const mockTx: CreateReferralSet = {
team,
};
const mockId = '123456';
const mockCreator = 'JohnDoe';
it('should render the team name', () => {
const { getByText } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByText('Test Team')).toBeInTheDocument();
});
it('should render the team ID', () => {
const { getByText } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByText('Id')).toBeInTheDocument();
expect(getByText(mockId)).toBeInTheDocument();
});
it('should render the creator', () => {
const { getByText } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByText('Creator')).toBeInTheDocument();
expect(getByText(mockCreator)).toBeInTheDocument();
});
it('should render the team URL', () => {
const { getByText } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByText('Team URL')).toBeInTheDocument();
expect(getByText(team.teamUrl)).toBeInTheDocument();
});
it('should render the avatar URL', () => {
const { getByText } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByText('Avatar')).toBeInTheDocument();
expect(getByText(team.avatarUrl)).toBeInTheDocument();
});
it('should render the open status as a tick if closed is falsy', () => {
const { getByTestId } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByTestId('open-yes')).toBeInTheDocument();
});
it('should render the open status as a cross if it is truthy', () => {
const m = {
team: {
closed: true,
},
};
const { getByTestId } = render(
<MockedProvider>
<ReferralTeam tx={m} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByTestId('open-no')).toBeInTheDocument();
});
});
@@ -1,73 +0,0 @@
import {
VegaIcon,
Icon,
KeyValueTable,
KeyValueTableRow,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import type { components } from '../../../../../types/explorer';
import Hash from '../../../links/hash';
import { t } from 'i18next';
import { PartyLink } from '../../../links';
export type CreateReferralSet = components['schemas']['v1CreateReferralSet'];
export type ReferralTeam = CreateReferralSet['team'];
export interface ReferralTeamProps {
tx: CreateReferralSet;
id: string;
creator: string;
}
/**
* Renders the details for a team in a CreateReferralSet or UpdateReferralSet transaction.
*
* Intentionally does not render the avatar image or link to the team url.
*/
export const ReferralTeam = ({ tx, id, creator }: ReferralTeamProps) => {
if (!tx.team) {
return null;
}
return (
<section>
<div className="inline-block mr-2 leading-none">
<VegaIcon name={VegaIconNames.TEAM} />
</div>
{tx.team.name && (
<h3 className="inline-block leading-loose">{tx.team.name}</h3>
)}
<div className="min-w-fit max-w-2xl block">
<KeyValueTable>
<KeyValueTableRow>
{t('Id')}
<Hash text={id} truncate={false} />
</KeyValueTableRow>
<KeyValueTableRow>
{t('Creator')}
<PartyLink id={creator} truncate={false} />
</KeyValueTableRow>
{tx.team.teamUrl && (
<KeyValueTableRow>
{t('Team URL')}
<Hash text={tx.team.teamUrl} truncate={false} />
</KeyValueTableRow>
)}
{tx.team.avatarUrl && (
<KeyValueTableRow>
{t('Avatar')}
<Hash text={tx.team.avatarUrl} truncate={false} />
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('Open')}
<span data-testid={!tx.team.closed ? 'open-yes' : 'open-no'}>
{!tx.team.closed ? <Icon name="tick" /> : <Icon name="cross" />}
</span>
</KeyValueTableRow>
</KeyValueTable>
</div>
</section>
);
};
@@ -27,8 +27,7 @@ export const sharedHeaderProps = {
className: 'align-top',
};
// The incoming type field is usually the right thing to show. Exceptions are listed here
const LabelOverrides: Record<BlockExplorerTransactionResult['type'], string> = {
const Labels: Record<BlockExplorerTransactionResult['type'], string> = {
'Stop Orders Submission': 'Stop Order',
'Stop Orders Cancellation': 'Cancel Stop Order',
};
@@ -51,7 +50,7 @@ export const TxDetailsShared = ({
const time: string = blockData?.result.block.header.time || '';
const height: string = blockData?.result.block.header.height || txData.block;
const type = LabelOverrides[txData.type] || txData.type;
const type = Labels[txData.type] || txData.type;
return (
<>
@@ -1,18 +0,0 @@
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
transfer {
reference
timestamp
status
reason
fromAccountType
from
to
toAccountType
asset {
id
}
amount
}
}
}
@@ -1,61 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerTransferStatusQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerTransferStatusQuery = { __typename?: 'Query', transfer?: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', reference?: string | null, timestamp: any, status: Types.TransferStatus, reason?: string | null, fromAccountType: Types.AccountType, from: string, to: string, toAccountType: Types.AccountType, amount: string, asset?: { __typename?: 'Asset', id: string } | null } } | null };
export const ExplorerTransferStatusDocument = gql`
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
transfer {
reference
timestamp
status
reason
fromAccountType
from
to
toAccountType
asset {
id
}
amount
}
}
}
`;
/**
* __useExplorerTransferStatusQuery__
*
* To run a query within a React component, call `useExplorerTransferStatusQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerTransferStatusQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerTransferStatusQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerTransferStatusQuery(baseOptions: Apollo.QueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
}
export function useExplorerTransferStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
}
export type ExplorerTransferStatusQueryHookResult = ReturnType<typeof useExplorerTransferStatusQuery>;
export type ExplorerTransferStatusLazyQueryHookResult = ReturnType<typeof useExplorerTransferStatusLazyQuery>;
export type ExplorerTransferStatusQueryResult = Apollo.QueryResult<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>;
@@ -41,7 +41,6 @@ const AccountType: Record<AccountTypes, string> = {
ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY: 'Reward Return Volatility',
ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING: 'Reward Validator Ranking',
ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD: 'Pending Fee Referral Reward',
ACCOUNT_TYPE_ORDER_MARGIN: 'Order Margin',
};
interface TransferParticipantsProps {
@@ -111,7 +110,7 @@ export function TransferParticipants({
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 9"
className="fill-white dark:fill-black"
className="fill-vega-light-100 dark:fill-black"
>
<path d="M0,0L8,9l8,-9Z" />
</svg>
@@ -120,7 +119,7 @@ export function TransferParticipants({
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 9"
className="fill-vega-light-200 dark:fill-vega-dark-200"
className="fill-vega-light-100 dark:fill-vega-dark-200"
>
<path d="M0,0L8,9l8,-9Z" />
</svg>
@@ -1,223 +1,97 @@
import { t } from '@vegaprotocol/i18n';
import { AssetLink, MarketLink } from '../../../../links';
import { headerClasses, wrapperClasses } from '../transfer-details';
import type { components } from '../../../../../../types/explorer';
import type { Recurring } from '../transfer-details';
import {
DispatchMetricLabels,
DistributionStrategy,
} from '@vegaprotocol/types';
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { formatNumber } from '@vegaprotocol/utils';
import { DispatchMetricLabels } from '@vegaprotocol/types';
export type Metric = components['schemas']['vegaDispatchMetric'];
export type Strategy = components['schemas']['vegaDispatchStrategy'];
export const wrapperClasses = 'border pv-2 w-full flex-auto basis-full';
export const headerClasses =
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 text-center text-xl py-2 font-alpha calt';
const metricLabels: Record<Metric, string> = {
DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric',
...DispatchMetricLabels,
};
// Maps the two (non-null) values of entityScope to the icon that represents it
const entityScopeIcons: Record<
string,
typeof VegaIconNames[keyof typeof VegaIconNames]
> = {
ENTITY_SCOPE_INDIVIDUALS: VegaIconNames.MAN,
ENTITY_SCOPE_TEAMS: VegaIconNames.TEAM,
};
const distributionStrategyLabel: Record<DistributionStrategy, string> = {
[DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA]: 'Pro Rata',
[DistributionStrategy.DISTRIBUTION_STRATEGY_RANK]: 'Ranked',
};
interface TransferRewardsProps {
recurring: Recurring;
}
/**
* Renders recurring transfers/game details in a way that is, perhaps, easy to understand
* Renderer for a transfer. These can vary quite
* widely, essentially every field can be null.
*
* @param transfer A recurring transfer object
*/
export function TransferRewards({ recurring }: TransferRewardsProps) {
const metric =
recurring?.dispatchStrategy?.metric || 'DISPATCH_METRIC_UNSPECIFIED';
if (!recurring || !recurring.dispatchStrategy) {
return null;
}
// Destructure to make things a bit more readable
const {
entityScope,
individualScope,
teamScope,
distributionStrategy,
lockPeriod,
markets,
stakingRequirement,
windowLength,
notionalTimeWeightedAveragePositionRequirement,
rankTable,
nTopPerformers,
} = recurring.dispatchStrategy;
return (
<div className={wrapperClasses}>
<h2 className={headerClasses}>{getRewardTitle(entityScope)}</h2>
<ul className="relative block rounded-lg py-6 text-left p-6">
{entityScope && entityScopeIcons[entityScope] ? (
<h2 className={headerClasses}>{t('Reward metrics')}</h2>
<ul className="relative block rounded-lg py-6 text-center p-6">
{recurring.dispatchStrategy.assetForMetric ? (
<li>
<strong>{t('Scope')}</strong>:{' '}
<VegaIcon name={entityScopeIcons[entityScope]} />
&nbsp;
{individualScope ? individualScopeLabels[individualScope] : null}
{getScopeLabel(entityScope, teamScope)}
<strong>{t('Asset')}</strong>:{' '}
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
</li>
) : null}
{recurring.dispatchStrategy &&
recurring.dispatchStrategy.assetForMetric && (
<li>
<strong>{t('Asset for metric')}</strong>:{' '}
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
</li>
)}
{recurring.dispatchStrategy.metric &&
metricLabels[recurring.dispatchStrategy.metric] && (
<li>
<strong>{t('Metric')}</strong>:{' '}
{metricLabels[recurring.dispatchStrategy.metric]}
</li>
)}
{lockPeriod && (
<li>
<strong>{t('Reward lock')}</strong>:&nbsp;
{recurring.dispatchStrategy.lockPeriod}{' '}
{recurring.dispatchStrategy.lockPeriod === '1'
? t('epoch')
: t('epochs')}
</li>
)}
{markets && markets.length > 0 ? (
<li>
<strong>{t('Metric')}</strong>: {metricLabels[metric]}
</li>
{recurring.dispatchStrategy.markets &&
recurring.dispatchStrategy.markets.length > 0 ? (
<li>
<strong>{t('Markets in scope')}</strong>:
<ul className="inline-block ml-1">
{markets.map((m) => (
<li key={m} className="inline-block mr-2">
<ul>
{recurring.dispatchStrategy.markets.map((m) => (
<li key={m}>
<MarketLink id={m} />
</li>
))}
</ul>
</li>
) : null}
{stakingRequirement && stakingRequirement !== '0' ? (
<li>
<strong>{t('Staking requirement')}</strong>: {stakingRequirement}
</li>
) : null}
{windowLength && windowLength !== '0' ? (
<li>
<strong>{t('Window length')}</strong>:{' '}
{recurring.dispatchStrategy.windowLength}{' '}
{recurring.dispatchStrategy.windowLength === '1'
? t('epoch')
: t('epochs')}
</li>
) : null}
{notionalTimeWeightedAveragePositionRequirement &&
notionalTimeWeightedAveragePositionRequirement !== '' ? (
<li>
<strong>{t('Notional TWAP')}</strong>:{' '}
{notionalTimeWeightedAveragePositionRequirement}
</li>
) : null}
{nTopPerformers && (
<li>
<strong>{t('Elligible team members:')}</strong> top{' '}
{`${formatNumber(Number(nTopPerformers) * 100, 0)}%`}
</li>
)}
{distributionStrategy &&
distributionStrategy !== 'DISTRIBUTION_STRATEGY_UNSPECIFIED' && (
<li>
<strong>{t('Distribution strategy')}</strong>:{' '}
{distributionStrategyLabel[distributionStrategy]}
</li>
)}
<li>
<strong>{t('Factor')}</strong>: {recurring.factor}
</li>
</ul>
<div className="px-6 pt-1 pb-5">
{rankTable && rankTable.length > 0 ? (
<table className="border-collapse border border-gray-400 ">
<thead>
<tr>
<th className="border border-gray-300 bg-gray-300 px-3">
<strong>{t('Start rank')}</strong>
</th>
<th className="border border-gray-300 bg-gray-300 px-3">
<strong>{t('Share of reward pool')}</strong>
</th>
</tr>
</thead>
<tbody>
{rankTable.map((row, i) => {
return (
<tr key={`rank-${i}`}>
<td className="border border-slate-300 text-center">
{row.startRank}
</td>
<td className="border border-slate-300 text-center">
{row.shareRatio}
</td>
</tr>
);
})}
</tbody>
</table>
) : null}
</div>
</div>
);
}
export function getScopeLabel(
scope: components['schemas']['vegaEntityScope'] | undefined,
teamScope: readonly string[] | undefined
): string {
if (scope === 'ENTITY_SCOPE_TEAMS') {
if (teamScope && teamScope.length !== 0) {
return ` ${teamScope.length} teams`;
} else {
return t('All teams');
}
} else if (scope === 'ENTITY_SCOPE_INDIVIDUALS') {
return t('Individuals');
} else {
return '';
}
}
export function getRewardTitle(
scope?: components['schemas']['vegaEntityScope']
) {
if (scope === 'ENTITY_SCOPE_TEAMS') {
return t('Game');
}
return t('Reward metrics');
interface TransferRecurringStrategyProps {
strategy: Strategy;
}
const individualScopeLabels: Record<
components['schemas']['vegaIndividualScope'],
string
> = {
// Unspecified and All are not rendered
INDIVIDUAL_SCOPE_UNSPECIFIED: '',
INDIVIDUAL_SCOPE_ALL: '',
INDIVIDUAL_SCOPE_IN_TEAM: '(in team)',
INDIVIDUAL_SCOPE_NOT_IN_TEAM: '(not in team)',
};
/**
* Simple renderer for a dispatch strategy in a recurring transfer
*
* @param strategy Dispatch strategy object
*/
export function TransferRecurringStrategy({
strategy,
}: TransferRecurringStrategyProps) {
if (!strategy) {
return null;
}
return (
<>
{strategy.assetForMetric ? (
<li>
<strong>{t('Asset for metric')}</strong>:{' '}
<AssetLink assetId={strategy.assetForMetric} />
</li>
) : null}
<li>
<strong>{t('Metric')}</strong>: {strategy.metric}
</li>
</>
);
}
@@ -1,100 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { headerClasses, wrapperClasses } from '../transfer-details';
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@vegaprotocol/ui-toolkit';
import type { ApolloError } from '@apollo/client';
import { TransferStatus, TransferStatusMapping } from '@vegaprotocol/types';
import { IconNames } from '@blueprintjs/icons';
interface TransferStatusProps {
status: TransferStatus | undefined;
error: ApolloError | undefined;
loading: boolean;
}
/**
* Renderer for a transfer. These can vary quite
* widely, essentially every field can be null.
*
* @param transfer A recurring transfer object
*/
export function TransferStatusView({ status, loading }: TransferStatusProps) {
if (!status) {
status = TransferStatus.STATUS_PENDING;
}
return (
<div className={wrapperClasses}>
<h2 className={headerClasses}>{t('Status')}</h2>
<div className="relative block rounded-lg py-6 text-center p-6">
{loading ? (
<div className="leading-10 mt-12">
<Loader size={'small'} />
</div>
) : (
<>
<p className="leading-10 my-2">
<TransferStatusIcon status={status} />
</p>
<p className="leading-10 my-2">{TransferStatusMapping[status]}</p>
</>
)}
</div>
</div>
);
}
interface TransferStatusIconProps {
status: TransferStatus;
}
export function TransferStatusIcon({ status }: TransferStatusIconProps) {
return (
<span title={TransferStatusMapping[status]}>
<Icon
name={getIconForStatus(status)}
className={getColourForStatus(status)}
/>
</span>
);
}
/**
* Simple mapping from status to icon name
* @param status TransferStatus
* @returns IconName
*/
export function getIconForStatus(status: TransferStatus): IconName {
switch (status) {
case TransferStatus.STATUS_PENDING:
return IconNames.TIME;
case TransferStatus.STATUS_DONE:
return IconNames.TICK;
case TransferStatus.STATUS_REJECTED:
return IconNames.CROSS;
case TransferStatus.STATUS_CANCELLED:
return IconNames.CROSS;
default:
return IconNames.TIME;
}
}
/**
* Simple mapping from status to colour
* @param status TransferStatus
* @returns string Tailwind classname
*/
export function getColourForStatus(status: TransferStatus): string {
switch (status) {
case TransferStatus.STATUS_PENDING:
return 'text-yellow-500';
case TransferStatus.STATUS_DONE:
return 'text-green-500';
case TransferStatus.STATUS_REJECTED:
return 'text-red-500';
case TransferStatus.STATUS_CANCELLED:
return 'text-red-600';
default:
return 'text-yellow-500';
}
}
@@ -2,15 +2,12 @@ import type { components } from '../../../../../types/explorer';
import { TransferRepeat } from './blocks/transfer-repeat';
import { TransferRewards } from './blocks/transfer-rewards';
import { TransferParticipants } from './blocks/transfer-participants';
import { useExplorerTransferStatusQuery } from './__generated__/Transfer';
import { TransferStatusView } from './blocks/transfer-status';
import { TransferStatus } from '@vegaprotocol/types';
export type Recurring = components['schemas']['commandsv1RecurringTransfer'];
export type Metric = components['schemas']['vegaDispatchMetric'];
export const wrapperClasses =
'border border-vega-light-150 dark:border-vega-dark-200 pv-2 w-full sm:w-1/3 basis-1/3';
'border border-vega-light-150 dark:border-vega-dark-200 rounded-md pv-2 mb-5 w-full sm:w-1/4 min-w-[200px] ';
export const headerClasses =
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 border-vega-light-150 text-center text-xl py-2 font-alpha calt';
@@ -19,7 +16,6 @@ export type Transfer = components['schemas']['commandsv1Transfer'];
interface TransferDetailsProps {
transfer: Transfer;
from: string;
id: string;
}
/**
@@ -28,24 +24,13 @@ interface TransferDetailsProps {
*
* @param transfer A recurring transfer object
*/
export function TransferDetails({ transfer, from, id }: TransferDetailsProps) {
export function TransferDetails({ transfer, from }: TransferDetailsProps) {
const recurring = transfer.recurring;
// Currently all this is passed in to TransferStatus, but the extra details
// may be useful in the future.
const { data, error, loading } = useExplorerTransferStatusQuery({
variables: { id },
});
const status = error
? TransferStatus.STATUS_REJECTED
: data?.transfer?.transfer.status;
return (
<div className="flex flex-wrap">
<div className="flex gap-5 flex-wrap">
<TransferParticipants from={from} transfer={transfer} />
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
<TransferStatusView status={status} error={error} loading={loading} />
{recurring && recurring.dispatchStrategy ? (
<TransferRewards recurring={transfer.recurring} />
) : null}
@@ -1,172 +0,0 @@
import {
getScopeLabel,
getRewardTitle,
TransferRewards,
} from './blocks/transfer-rewards';
import { render } from '@testing-library/react';
import type { components } from '../../../../../types/explorer';
import type { Recurring } from './transfer-details';
import {
DispatchMetric,
DistributionStrategy,
EntityScope,
IndividualScope,
} from '@vegaprotocol/types';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
describe('getScopeLabel', () => {
it('should return the correct label for ENTITY_SCOPE_TEAMS with teamScope', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const teamScope = ['team1', 'team2', 'team3'];
const expectedLabel = ' 3 teams';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return the correct label for ENTITY_SCOPE_TEAMS without teamScope', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const teamScope = undefined;
const expectedLabel = 'All teams';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return the correct label for ENTITY_SCOPE_INDIVIDUALS', () => {
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
const teamScope = undefined;
const expectedLabel = 'Individuals';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return an empty string for unknown scope', () => {
const scope = 'UNKNOWN_SCOPE';
const teamScope = undefined;
const expectedLabel = '';
const result = getScopeLabel(
scope as unknown as components['schemas']['vegaEntityScope'],
teamScope
);
expect(result).toEqual(expectedLabel);
});
});
describe('getRewardTitle', () => {
it('should return the correct title for ENTITY_SCOPE_TEAMS', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const expectedTitle = 'Game';
const result = getRewardTitle(scope);
expect(result).toEqual(expectedTitle);
});
it('should return the correct title for other scopes', () => {
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
const expectedTitle = 'Reward metrics';
const result = getRewardTitle(scope);
expect(result).toEqual(expectedTitle);
});
});
describe('TransferRewards', () => {
it('should render nothing if recurring dispatchStrategy is not provided', () => {
const { container } = render(
<TransferRewards recurring={null as unknown as Recurring} />
);
expect(container.firstChild).toBeNull();
});
it('should render nothing if recurring.dispatchStrategy is not provided', () => {
const { container } = render(
<TransferRewards recurring={{} as unknown as Recurring} />
);
expect(container.firstChild).toBeNull();
});
it('should render the reward details correctly', () => {
const recurring = {
dispatchStrategy: {
metric: DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION,
assetForMetric: '123',
entityScope: EntityScope.ENTITY_SCOPE_TEAMS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM,
teamScope: [],
distributionStrategy:
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
lockPeriod: 'lockPeriod',
markets: ['market1', 'market2'],
stakingRequirement: '1',
windowLength: 'windowLength',
notionalTimeWeightedAveragePositionRequirement:
'notionalTimeWeightedAveragePositionRequirement',
rankTable: [
{ startRank: 1, shareRatio: 0.2 },
{ startRank: 2, shareRatio: 0.3 },
],
nTopPerformers: 'nTopPerformers',
},
};
const { getByText } = render(
<MemoryRouter>
<MockedProvider>
<TransferRewards recurring={recurring} />
</MockedProvider>
</MemoryRouter>
);
expect(getByText('Game')).toBeInTheDocument();
expect(getByText('Scope')).toBeInTheDocument();
expect(getByText('Asset for metric')).toBeInTheDocument();
expect(getByText('Metric')).toBeInTheDocument();
expect(getByText('Reward lock')).toBeInTheDocument();
expect(getByText('Markets in scope')).toBeInTheDocument();
expect(getByText('Staking requirement')).toBeInTheDocument();
expect(getByText('Window length')).toBeInTheDocument();
expect(getByText('Notional TWAP')).toBeInTheDocument();
expect(getByText('Elligible team members:')).toBeInTheDocument();
expect(getByText('Distribution strategy')).toBeInTheDocument();
expect(getByText('Start rank')).toBeInTheDocument();
expect(getByText('Share of reward pool')).toBeInTheDocument();
});
it('should not render a rank table if recurring.dispatchStrategy.rankTable is not provided', () => {
const recurring = {
dispatchStrategy: {
entityScope: EntityScope.ENTITY_SCOPE_INDIVIDUALS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_ALL,
teamScope: ['team1', 'team2', 'team3'],
distributionStrategy:
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
lockPeriod: 'lockPeriod',
markets: ['market1', 'market2'],
stakingRequirement: 'stakingRequirement',
windowLength: 'windowLength',
notionalTimeWeightedAveragePositionRequirement:
'notionalTimeWeightedAveragePositionRequirement',
nTopPerformers: 'nTopPerformers',
},
};
const { container } = render(
<MemoryRouter>
<MockedProvider>
<TransferRewards recurring={recurring} />
</MockedProvider>
</MemoryRouter>
);
expect(container.querySelector('table')).toBeNull();
});
});
@@ -1,47 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { ReferralCodeOwner } from './referrals/referral-code-owner';
interface TxDetailsApplyReferralCodeProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* The signature can be turned in to an id with txSignatureToDeterministicId but
*/
export const TxDetailsApplyReferralCode = ({
txData,
pubKey,
blockData,
}: TxDetailsApplyReferralCodeProps) => {
if (!txData || !txData.command.applyReferralCode || !txData.signature) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const referralCode = txData.command.applyReferralCode.id;
return (
<div>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{t('Applied Code')}</TableCell>
<TableCell>{referralCode}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Referrer')}</TableCell>
<ReferralCodeOwner code={referralCode} />
</TableRow>
</TableWithTbody>
</div>
);
};
@@ -1,75 +0,0 @@
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { sharedHeaderProps, TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import { ProposalSummary } from './proposal/summary';
import Hash from '../../links/hash';
import { t } from '@vegaprotocol/i18n';
export type Proposal = components['schemas']['v1BatchProposalSubmission'];
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
interface TxBatchProposalProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
*
*/
export const TxBatchProposal = ({
txData,
pubKey,
blockData,
}: TxBatchProposalProps) => {
if (!txData || !txData.command.batchProposalSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
let deterministicId = '';
const proposal: Proposal = txData.command.batchProposalSubmission;
const sig = txData?.signature?.value;
if (sig) {
deterministicId = txSignatureToDeterministicId(sig);
}
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{t('Batch proposal')}</TableCell>
</TableRow>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
hideTypeRow={true}
/>
<TableRow modifier="bordered">
<TableCell>{t('Batch size')}</TableCell>
<TableCell>
{proposal.terms?.changes?.length || t('No changes')}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Proposal ID')}</TableCell>
<TableCell>
<Hash text={deterministicId} />
</TableCell>
</TableRow>
</TableWithTbody>
{proposal && (
<ProposalSummary
id={deterministicId}
rationale={proposal?.rationale}
terms={proposal.terms}
batch={proposal.terms?.changes}
/>
)}
</>
);
};
@@ -1,52 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import { ReferralTeam } from './referrals/team';
interface TxDetailsCreateReferralProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* The signature can be turned in to an id with txSignatureToDeterministicId but
*/
export const TxDetailsCreateReferralSet = ({
txData,
pubKey,
blockData,
}: TxDetailsCreateReferralProps) => {
if (!txData || !txData.command.createReferralSet || !txData.signature) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const id = txSignatureToDeterministicId(txData.signature.value);
const isTeam = txData.command.createReferralSet.isTeam || false;
return (
<div>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{isTeam ? t('Team ID') : t('Referral code')}</TableCell>
<TableCell>{id}</TableCell>
</TableRow>
</TableWithTbody>
<ReferralTeam
tx={txData.command.createReferralSet}
id={id}
creator={txData.submitter}
/>
</div>
);
};
@@ -28,13 +28,6 @@ import { TxProposal } from './tx-proposal';
import { TxDetailsTransfer } from './tx-transfer';
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import { TxDetailsCreateReferralSet } from './tx-create-referral-set';
import { TxDetailsApplyReferralCode } from './tx-apply-referral-code';
import { TxDetailsUpdateReferralSet } from './tx-update-referral-set';
import { TxDetailsJoinTeam } from './tx-join-team';
import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode';
import { TxBatchProposal } from './tx-batch-proposal';
import { TxDetailsUpdatePartyProfile } from './proposal/tx-update-party-profile';
interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -128,20 +121,6 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsStopOrderSubmission;
case 'Transfer Funds':
return TxDetailsTransfer;
case 'Create Referral Set':
return TxDetailsCreateReferralSet;
case 'Update Referral Set':
return TxDetailsUpdateReferralSet;
case 'Apply Referral Code':
return TxDetailsApplyReferralCode;
case 'Join Team':
return TxDetailsJoinTeam;
case 'Update Margin Mode':
return TxDetailsUpdateMarginMode;
case 'Batch Proposal':
return TxBatchProposal;
case 'Update Party Profile':
return TxDetailsUpdatePartyProfile;
default:
return TxDetailsGeneric;
}
@@ -5,9 +5,9 @@ import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../links/external-explorer-link/external-explorer-link';
} from '../../links/eth-explorer-link/eth-explorer-link';
import { BlockLink } from '../../links';
type EthKeyRotate = components['schemas']['v1EthereumKeyRotateSubmission'];
@@ -46,7 +46,7 @@ export const TxDetailsEthKeyRotate = ({
<TableRow modifier="bordered">
<TableCell>{t('Old Address')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
type={EthExplorerLinkTypes.address}
id={k.currentAddress}
/>
@@ -57,7 +57,7 @@ export const TxDetailsEthKeyRotate = ({
<TableRow modifier="bordered">
<TableCell>{t('New Address')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
type={EthExplorerLinkTypes.address}
id={k.newAddress}
/>
@@ -68,7 +68,7 @@ export const TxDetailsEthKeyRotate = ({
<TableRow modifier="bordered">
<TableCell>{t('Submitter address')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
type={EthExplorerLinkTypes.address}
id={k.submitterAddress}
/>
@@ -6,9 +6,9 @@ import { TableRow, TableCell, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../links/external-explorer-link/external-explorer-link';
} from '../../links/eth-explorer-link/eth-explorer-link';
import { NodeLink } from '../../links';
type Command = components['schemas']['v1IssueSignatures'];
@@ -57,7 +57,7 @@ export const TxDetailsIssueSignatures = ({
<TableRow modifier="bordered">
<TableCell>{t('ETH key')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
id={cmd.submitter}
type={EthExplorerLinkTypes.address}
/>
@@ -1,47 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { ReferralCodeOwner } from './referrals/referral-code-owner';
interface TxDetailsJoinTeamProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* The signature can be turned in to an id with txSignatureToDeterministicId but
*/
export const TxDetailsJoinTeam = ({
txData,
pubKey,
blockData,
}: TxDetailsJoinTeamProps) => {
if (!txData || !txData.command.joinTeam || !txData.signature) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const team = txData.command.joinTeam.id;
return (
<div>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{t('Team')}</TableCell>
<TableCell>{team}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Referrer')}</TableCell>
<ReferralCodeOwner code={team} />
</TableRow>
</TableWithTbody>
</div>
);
};
@@ -6,9 +6,9 @@ import { TableRow, TableCell, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../links/external-explorer-link/external-explorer-link';
} from '../../links/eth-explorer-link/eth-explorer-link';
import { PartyLink } from '../../links';
import Hash from '../../links/hash';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
@@ -72,7 +72,7 @@ export const TxDetailsNodeAnnounce = ({
<TableRow modifier="bordered">
<TableCell>{t('Ethereum Address')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
type={EthExplorerLinkTypes.address}
id={cmd.ethereumAddress}
/>
@@ -8,9 +8,9 @@ import { useExplorerNodeVoteQuery } from './__generated__/Node-vote';
import { PartyLink } from '../../links';
import { Time } from '../../time';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../links/external-explorer-link/external-explorer-link';
} from '../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsNodeVoteProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -143,7 +143,7 @@ export function TxHash({ hash }: TxDetailsEthTxHashProps) {
<TableRow modifier="bordered">
<TableCell>Ethereum TX:</TableCell>
<TableCell>
<ExternalExplorerLink id={hash} type={EthExplorerLinkTypes.tx} />
<EthExplorerLink id={hash} type={EthExplorerLinkTypes.tx} />
</TableCell>
</TableRow>
);
@@ -12,8 +12,6 @@ import { ProposalSignatureBundleNewAsset } from './proposal/signature-bundle-new
import { ProposalSignatureBundleUpdateAsset } from './proposal/signature-bundle-update';
import { MarketLink } from '../../links';
import { formatNumber } from '@vegaprotocol/utils';
import { TransferDetails } from './transfer/transfer-details';
import { proposalToTransfer } from '../lib/proposal-to-transfer';
export type Proposal = components['schemas']['v1ProposalSubmission'];
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
@@ -106,12 +104,6 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
? ProposalSignatureBundleNewAsset
: ProposalSignatureBundleUpdateAsset;
let transfer, from;
if (proposal.terms?.newTransfer?.changes) {
transfer = proposalToTransfer(proposal.terms?.newTransfer.changes);
from = proposal.terms.newTransfer.changes.source;
}
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
@@ -157,26 +149,14 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
</>
) : null}
</TableWithTbody>
<ProposalSummary
id={deterministicId}
rationale={proposal.rationale}
terms={proposal?.terms}
/>
{proposalRequiresSignatureBundle(proposal) && (
<SignatureBundleComponent id={deterministicId} tx={tx} />
)}
{transfer && (
<div className="mt-8">
<TransferDetails
transfer={transfer}
from={from || ''}
id={deterministicId}
/>
</div>
)}
</>
);
};
@@ -13,7 +13,6 @@ import {
SPECIAL_CASE_NETWORK_ID,
} from '../../links/party-link/party-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import Hash from '../../links/hash';
type Transfer = components['schemas']['commandsv1Transfer'];
@@ -61,7 +60,7 @@ export const TxDetailsTransfer = ({
}
const from = txData.submitter;
const id = txSignatureToDeterministicId(txData.signature.value);
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
@@ -72,7 +71,7 @@ export const TxDetailsTransfer = ({
<TableRow modifier="bordered" data-testid="id">
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
<TableCell>
<Hash text={id} />
{txSignatureToDeterministicId(txData.signature.value)}
</TableCell>
</TableRow>
<TxDetailsShared
@@ -106,7 +105,7 @@ export const TxDetailsTransfer = ({
</TableRow>
) : null}
</TableWithTbody>
<TransferDetails from={from} transfer={transfer} id={id} />
<TransferDetails from={from} transfer={transfer} />
</>
);
};
@@ -8,7 +8,7 @@ import GovernanceAssetBalance from '../../asset-balance/governance-asset-balance
import type { components } from '../../../../types/explorer';
export const methodText: Record<
components['schemas']['v1UndelegateSubmissionMethod'],
components['schemas']['UndelegateSubmissionMethod'],
string
> = {
METHOD_NOW: 'Immediate',
@@ -1,60 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { MarketLink } from '../../links';
interface TxDetailsUpdateMarginModeProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
type Mode = components['schemas']['UpdateMarginModeMode'];
const MarginModeLabels: Record<Mode, string> = {
MODE_CROSS_MARGIN: t('Cross margin'),
MODE_ISOLATED_MARGIN: t('Isolated margin'),
MODE_UNSPECIFIED: t('Unspecified'),
};
export const TxDetailsUpdateMarginMode = ({
txData,
pubKey,
blockData,
}: TxDetailsUpdateMarginModeProps) => {
if (!txData || !txData.command.updateMarginMode) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const u: components['schemas']['v1UpdateMarginMode'] =
txData.command.updateMarginMode;
return (
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{u.marketId && (
<TableRow modifier="bordered">
<TableCell>{t('Market ID')}</TableCell>
<TableCell>
<MarketLink id={u.marketId} />
</TableCell>
</TableRow>
)}
{u.mode && (
<TableRow modifier="bordered">
<TableCell>{t('New margin mode')}</TableCell>
<TableCell>{MarginModeLabels[u.mode]}</TableCell>
</TableRow>
)}
{u.marginFactor && (
<TableRow modifier="bordered">
<TableCell>{t('Margin factor')}</TableCell>
<TableCell>{u.marginFactor}</TableCell>
</TableRow>
)}
</TableWithTbody>
);
};
@@ -1,54 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import { ReferralTeam } from './referrals/team';
interface TxDetailsUpdateReferralProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* A copy of create referral set, effectively.
* Updating a referral set without a team doesn't make sense,
* but is valid, so this component ignores sense.
*/
export const TxDetailsUpdateReferralSet = ({
txData,
pubKey,
blockData,
}: TxDetailsUpdateReferralProps) => {
if (!txData || !txData.command.updateReferralSet || !txData.signature) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const id = txSignatureToDeterministicId(txData.signature.value);
const isTeam = txData.command.updateReferralSet.isTeam || false;
return (
<div>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{isTeam ? t('Team ID') : t('Referral code')}</TableCell>
<TableCell>{id}</TableCell>
</TableRow>
</TableWithTbody>
<ReferralTeam
tx={txData.command.updateReferralSet}
id={id}
creator={txData.submitter}
/>
</div>
);
};
@@ -4,9 +4,9 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../links/external-explorer-link/external-explorer-link';
} from '../../links/eth-explorer-link/eth-explorer-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import AssetBalance from '../../asset-balance/asset-balance';
import { useScrollToLocation } from '../../../hooks/scroll-to-location';
@@ -57,7 +57,7 @@ export const TxDetailsWithdrawSubmission = ({
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<ExternalExplorerLink
<EthExplorerLink
id={w.ext.erc20.receiverAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -1,29 +0,0 @@
import type { components } from '../../../../types/explorer';
type TransferProposal = components['schemas']['vegaNewTransferConfiguration'];
type ActualTransfer = components['schemas']['commandsv1Transfer'];
/**
* Converts a governance proposal for a transfer in to a transfer command that the
* TransferDetails component can then render. The types are very similar, but do not
* map precisely to each other due to some missing fields and some different field
* names.
*
* @param proposal Governance proposal for a transfer
* @returns transfer a Transfer object as if it had been submitted
*/
export function proposalToTransfer(proposal: TransferProposal): ActualTransfer {
return {
amount: proposal.amount,
asset: proposal.asset,
// On a transfer, 'from' is determined by the submitter, so there is no 'from' field
// fromAccountType does exist and is just named differently on the proposal
fromAccountType: proposal.sourceType,
oneOff: proposal.oneOff,
recurring: proposal.recurring,
// There is no reference applied on governance initiated transfers
reference: '',
to: proposal.destination,
toAccountType: proposal.destinationType,
};
}
@@ -20,7 +20,6 @@ export type FilterOption =
| 'Amend Order'
| 'Apply Referral Code'
| 'Batch Market Instructions'
| 'Batch Proposal'
| 'Cancel LiquidityProvision Order'
| 'Cancel Order'
| 'Cancel Transfer Funds'
@@ -29,7 +28,6 @@ export type FilterOption =
| 'Delegate'
| 'Ethereum Key Rotate Submission'
| 'Issue Signatures'
| 'Join Team'
| 'Key Rotate Submission'
| 'Liquidity Provision Order'
| 'Node Signature'
@@ -44,61 +42,50 @@ export type FilterOption =
| 'Submit Order'
| 'Transfer Funds'
| 'Undelegate'
| 'Update Party Profile'
| 'Update Referral Set'
| 'Update Margin Mode'
| 'Validator Heartbeat'
| 'Vote on Proposal'
| 'Withdraw';
export const filterOptions: Record<string, FilterOption[]> = {
'Market Instructions': [
'Amend LiquidityProvision Order',
'Amend Order',
'Batch Market Instructions',
'Cancel LiquidityProvision Order',
'Cancel Order',
'Liquidity Provision Order',
'Stop Orders Submission',
'Stop Orders Cancellation',
'Submit Order',
'Update Margin Mode',
],
'Transfers and Withdrawals': [
'Transfer Funds',
'Cancel Transfer Funds',
'Withdraw',
],
Governance: [
'Batch Proposal',
'Delegate',
'Undelegate',
'Vote on Proposal',
'Proposal',
],
Referrals: [
'Apply Referral Code',
'Create Referral Set',
'Join Team',
'Update Party Profile',
'Update Referral Set',
],
'External Data': ['Chain Event', 'Submit Oracle Data'],
Validators: [
'Ethereum Key Rotate Submission',
'Issue Signatures',
'Key Rotate Submission',
'Node Signature',
'Node Vote',
'Protocol Upgrade',
'Register new Node',
'State Variable Proposal',
'Validator Heartbeat',
],
};
// Alphabetised list of transaction types to appear at the top level
export const PrimaryFilterOptions: FilterOption[] = [
'Amend LiquidityProvision Order',
'Amend Order',
'Batch Market Instructions',
'Cancel LiquidityProvision Order',
'Cancel Order',
'Cancel Transfer Funds',
'Delegate',
'Liquidity Provision Order',
'Proposal',
'Stop Orders Submission',
'Stop Orders Cancellation',
'Submit Oracle Data',
'Submit Order',
'Transfer Funds',
'Undelegate',
'Vote on Proposal',
'Withdraw',
];
export const AllFilterOptions: FilterOption[] =
Object.values(filterOptions).flat();
// Alphabetised list of transaction types to nest under a 'More...' submenu
export const SecondaryFilterOptions: FilterOption[] = [
'Chain Event',
'Ethereum Key Rotate Submission',
'Issue Signatures',
'Key Rotate Submission',
'Node Signature',
'Node Vote',
'Protocol Upgrade',
'Register new Node',
'State Variable Proposal',
'Validator Heartbeat',
];
export const AllFilterOptions: FilterOption[] = [
...PrimaryFilterOptions,
...SecondaryFilterOptions,
];
export interface TxFilterProps {
filters: Set<FilterOption>;
@@ -135,33 +122,46 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
<DropdownMenuSeparator />
</>
)}
{Object.entries(filterOptions).map(([key, value]) => (
<DropdownMenuSub key={key}>
<DropdownMenuSubTrigger>
{t(key)}
<Icon name="chevron-right" />
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{value.map((f) => (
<DropdownMenuCheckboxItem
key={f}
checked={filters.has(f)}
onCheckedChange={(checked) => {
// NOTE: These act like radio buttons until the API supports multiple filters
setFilters(new Set([f]));
}}
id={`radio-${f}`}
>
{f}
<DropdownMenuItemIndicator>
<Icon name="tick-circle" className="inline" />
</DropdownMenuItemIndicator>
</DropdownMenuCheckboxItem>
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
{PrimaryFilterOptions.map((f) => (
<DropdownMenuCheckboxItem
key={f}
checked={filters.has(f)}
onCheckedChange={() => {
// NOTE: These act like radio buttons until the API supports multiple filters
setFilters(new Set([f]));
}}
id={`radio-${f}`}
>
{f}
<DropdownMenuItemIndicator>
<Icon name="tick-circle" />
</DropdownMenuItemIndicator>
</DropdownMenuCheckboxItem>
))}
<DropdownMenuSub>
<DropdownMenuSubTrigger>
{t('More Types')}
<Icon name="chevron-right" />
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{SecondaryFilterOptions.map((f) => (
<DropdownMenuCheckboxItem
key={f}
checked={filters.has(f)}
onCheckedChange={(checked) => {
// NOTE: These act like radio buttons until the API supports multiple filters
setFilters(new Set([f]));
}}
id={`radio-${f}`}
>
{f}
<DropdownMenuItemIndicator>
<Icon name="tick-circle" className="inline" />
</DropdownMenuItemIndicator>
</DropdownMenuCheckboxItem>
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
);
@@ -61,4 +61,53 @@ describe('TxsListNavigation', () => {
expect(nextPageMock).toHaveBeenCalledTimes(1);
});
it('disables "Older" button if hasMoreTxs is false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={false}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Older')).toBeDisabled();
});
it('disables "Newer" button if hasPreviousPage is false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={true}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Newer')).toBeDisabled();
});
it('disables both buttons when more and previous are false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={false}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Newer')).toBeDisabled();
expect(screen.getByText('Older')).toBeDisabled();
});
});
@@ -10,8 +10,7 @@ export interface TxListNavigationProps {
loading?: boolean;
hasPreviousPage: boolean;
hasMoreTxs: boolean;
children?: React.ReactNode;
isEmpty?: boolean;
children: React.ReactNode;
}
/**
* Displays a list of transactions with filters and controls to navigate through the list.
@@ -22,8 +21,9 @@ export const TxsListNavigation = ({
refreshTxs,
nextPage,
previousPage,
hasMoreTxs,
hasPreviousPage,
children,
isEmpty,
loading = false,
}: TxListNavigationProps) => {
return (
@@ -35,6 +35,7 @@ export const TxsListNavigation = ({
<Button
className="mr-2"
size="xs"
disabled={!hasPreviousPage || loading}
onClick={() => {
previousPage();
}}
@@ -43,7 +44,7 @@ export const TxsListNavigation = ({
</Button>
<Button
size="xs"
disabled={isEmpty}
disabled={!hasMoreTxs}
onClick={() => {
nextPage();
}}
@@ -1,7 +1,6 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../types/explorer';
import { VoteIcon } from '../vote-icon/vote-icon';
import { ExternalChainIcon } from '../links/external-explorer-link/external-chain-icon';
interface TxOrderTypeProps {
orderType: string;
@@ -49,8 +48,6 @@ const displayString: StringMap = {
StopOrdersSubmission: 'Stop',
StopOrdersCancellation: 'Cancel stop',
'Stop Orders Cancellation': 'Cancel stop',
'Apply Referral Code': 'Referral',
'Create Referral Set': 'Create referral',
};
export function getLabelForStopOrderType(
@@ -189,9 +186,6 @@ export function getLabelForChainEvent(
}
return t('Multisig update');
} else if (chainEvent.contractCall) {
if (chainEvent.contractCall.error) {
return t('Call error');
}
return t('Contract call');
}
return t('Chain Event');
@@ -261,11 +255,6 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
data-testid="tx-type"
className={`text-sm rounded-md leading-tight px-2 inline-block whitespace-nowrap ${colours}`}
>
{command?.chainEvent && (
<ExternalChainIcon
chainId={command?.chainEvent?.contractCall?.sourceChainId}
/>
)}
{type}
</div>
);
+1
View File
@@ -14,6 +14,7 @@ export const ENV = {
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
tendermintUrl: windowOrDefault('NX_TENDERMINT_URL'),
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
ethExplorerUrl: windowOrDefault('NX_ETHERSCAN_URL'),
governanceUrl: windowOrDefault('NX_VEGA_GOVERNANCE_URL'),
vegaRepoUrl: windowOrDefault('NX_VEGA_REPO_URL'),
},
@@ -43,7 +43,7 @@ export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
url.searchParams.append('first', count);
url.searchParams.append('after', params.after);
} else {
url.searchParams.append('first', count);
url.searchParams.append('last', count);
}
// Hacky fix for param as array
@@ -6,7 +6,7 @@ describe('getTxsDataUrl', () => {
count: 10,
baseUrl: 'https://example.com/transactions',
};
const expectedUrl = 'https://example.com/transactions?first=10';
const expectedUrl = 'https://example.com/transactions?last=10';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
@@ -41,7 +41,7 @@ describe('getTxsDataUrl', () => {
baseUrl: 'https://example.com/transactions',
};
const expectedUrl =
'https://example.com/transactions?first=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
'https://example.com/transactions?last=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
+6 -6
View File
@@ -31,14 +31,14 @@ export interface IUseTxsData {
}
export const useTxsData = ({
count = 50,
count = 25,
before,
after,
filters,
party,
}: IUseTxsData) => {
const [, setSearchParams] = useSearchParams();
let hasMoreTxs = false;
let hasMoreTxs = true;
let txsData: BlockExplorerTransactionResult[] = [];
const url = getTxsDataUrl({
@@ -60,8 +60,8 @@ export const useTxsData = ({
}
const nextPage = useCallback(() => {
const before = data?.transactions.at(-1)?.cursor || '';
const params: URLSearchParamsInit = { before };
const after = data?.transactions.at(-1)?.cursor || '';
const params: URLSearchParamsInit = { after };
if (filters) {
params.filters = Array.from(filters).join(',');
}
@@ -69,8 +69,8 @@ export const useTxsData = ({
}, [filters, data, setSearchParams]);
const previousPage = useCallback(() => {
const after = data?.transactions[0]?.cursor || '';
const params: URLSearchParamsInit = { after };
const before = data?.transactions[0]?.cursor || '';
const params: URLSearchParamsInit = { before };
if (filters && filters.size > 0 && filters.size === 1) {
params.filters = Array.from(filters)[0];
}
+1 -1
View File
@@ -73,7 +73,7 @@ export const Layout = () => {
<ProtocolUpgradeInProgressNotification />
</div>
<div className={fixedWidthClasses}>
<main className="md:p-4">
<main className="p-4">
{!isHome && <BreadcrumbsContainer className="mb-4" />}
<Outlet />
</main>
@@ -78,7 +78,6 @@ fragment ExplorerOracleDataSource on OracleSpec {
}
}
}
sourceChainId
filters {
key {
name
@@ -119,7 +118,6 @@ fragment ExplorerOracleDataSource on OracleSpec {
address
requiredConfirmations
method
sourceChainId
filters {
key {
type
@@ -67,7 +67,6 @@ fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
sourceType {
... on EthCallSpec {
address
sourceChainId
}
... on DataSourceSpecConfiguration {
signers {
@@ -5,19 +5,19 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, sourceChainId: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, sourceChainId: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, sourceChainId: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
export const ExplorerOracleDataConnectionFragmentDoc = gql`
fragment ExplorerOracleDataConnection on OracleSpec {
@@ -101,7 +101,6 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
}
}
}
sourceChainId
filters {
key {
name
@@ -142,7 +141,6 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
address
requiredConfirmations
method
sourceChainId
filters {
key {
type
@@ -9,12 +9,12 @@ export type ExplorerOracleFutureFragment = { __typename?: 'Future', dataSourceSp
export type ExplorerOracleForMarketsMarketFragment = { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } };
export type ExplorerOracleDataSourceSpecFragment = { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } | { __typename?: 'EthCallSpec', address: string, sourceChainId: number } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } };
export type ExplorerOracleDataSourceSpecFragment = { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } | { __typename?: 'EthCallSpec', address: string } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } };
export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } | { __typename?: 'EthCallSpec', address: string, sourceChainId: number } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } | { __typename?: 'EthCallSpec', address: string } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export const ExplorerOracleFutureFragmentDoc = gql`
fragment ExplorerOracleFuture on Future {
@@ -90,7 +90,6 @@ export const ExplorerOracleDataSourceSpecFragmentDoc = gql`
sourceType {
... on EthCallSpec {
address
sourceChainId
}
... on DataSourceSpecConfiguration {
signers {
@@ -28,7 +28,7 @@ export function isInternalSourceType(s: SourceType) {
export function getExternalType(s: SourceType) {
if (s.sourceType.__typename === 'EthCallSpec') {
return 'Contract Call';
return 'Ethereum Contract Call';
} else {
return 'External Data';
}
@@ -1,24 +1,18 @@
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { SourceType } from './oracle';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../components/links/external-explorer-link/external-explorer-link';
import { getExternalChainLabel } from '../../../components/links/external-explorer-link/external-chain';
import { t } from 'i18next';
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
interface OracleDetailsEthSourceProps {
sourceType: SourceType;
chain?: string;
}
/**
* Given an Oracle that sources data from Ethereum, this component will render
* a link to the smart contract and some basic details
*/
export function OracleEthSource({
sourceType,
chain = '1',
}: OracleDetailsEthSourceProps) {
export function OracleEthSource({ sourceType }: OracleDetailsEthSourceProps) {
if (
sourceType.__typename !== 'DataSourceDefinitionExternal' ||
sourceType.sourceType.__typename !== 'EthCallSpec'
@@ -32,20 +26,11 @@ export function OracleEthSource({
return null;
}
const chainLabel = getExternalChainLabel(chain);
return (
<TableRow modifier="bordered">
<TableHeader scope="row">
{chainLabel} {t('Contract')}
</TableHeader>
<TableHeader scope="row">Ethereum Contract</TableHeader>
<TableCell modifier="bordered">
<ExternalExplorerLink
chain={chain}
id={address}
type={EthExplorerLinkTypes.address}
code={true}
/>
<EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />
<span className="mx-3">&rArr;</span>
<code>{sourceType.sourceType.method}</code>
</TableCell>
@@ -1,8 +1,8 @@
import { PartyLink } from '../../../components/links';
import {
ExternalExplorerLink,
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../components/links/external-explorer-link/external-explorer-link';
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import { remove0x } from '@vegaprotocol/utils';
@@ -37,15 +37,13 @@ export function getAddressLink(signer: Signer) {
}
if (signer.__typename === 'ETHAddress') {
return (
<ExternalExplorerLink id={address} type={EthExplorerLinkTypes.address} />
);
return <EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />;
} else if (signer.__typename === 'PubKey' && address.length !== 64) {
// This is a hack: some older oracles were submitted before proper checks stopped
// ETH addresses being returned as Vega addresses
// Hacky 0x prefixing as a bonus
return (
<ExternalExplorerLink
<EthExplorerLink
id={`0x${remove0x(address)}`}
type={EthExplorerLinkTypes.address}
/>
@@ -63,10 +61,7 @@ interface OracleDetailsSignersProps {
/**
* Given an Oracle, this component will render either a link to Ethereum
* or the Vega party depending on which type is specified.
*
* Note that this won't be shown for external contract calls as they do not
* have a signer in the same way.
* or the Vega party depending on which type is specified
*/
export function OracleSigners({ sourceType }: OracleDetailsSignersProps) {
if (sourceType.__typename !== 'DataSourceDefinitionExternal') {

Some files were not shown because too many files have changed in this diff Show More