Compare commits

...
Author SHA1 Message Date
Dexter 8fe0ee3b55 chore: correct button text colors 2023-07-14 16:49:32 +01:00
Dexter ab7c9c433d chore: align checkbox at start for multiline 2023-07-14 16:49:32 +01:00
Dexter 090ac59eac chore: do not override broder color 2023-07-14 16:49:32 +01:00
Mikołaj Młodzikowski f440f57be2 fix(ci): do not trigger releasing to s3 on pull requests 2023-07-14 13:45:06 +02:00
Mikołaj Młodzikowski edbdbcf38e feat(ci): provide fixes for releasing individual apps 2023-07-13 16:10:07 +02:00
Mikołaj Młodzikowski 94a067e34b fix(ci): disable pull_request_target (#4315) 2023-07-13 11:19:37 +02:00
Joe Tsang 8b6b904cd6 test(governance): un-skip governance tests (#4292) 2023-07-13 10:10:48 +01:00
Mikołaj Młodzikowski 27cd8086e1 fix(ci): syntax on switch case 2023-07-12 17:47:46 +02:00
Mikołaj Młodzikowski 6aa109131e feat(ci): allow individual releases of the applications (#4300) 2023-07-12 17:37:49 +02:00
Ben 74777e54f9 chore(ci): revert run console-test on CICD (#4303) 2023-07-12 16:26:52 +01:00
Ben 61e7450906 feat(trading): run console-test on CICD (#4271) 2023-07-12 12:06:48 +01:00
Matthew Russell 6a55319e04 feat(trading,governance,wallet): browser wallet integration (#4121) 2023-07-12 11:34:42 +01:00
Matthew Russell 5692d4e74c Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into develop 2023-07-12 11:01:22 +01:00
Maciek ffe89d0fe0 feat(trading): add market banner for successor markets (#4269) 2023-07-12 11:20:01 +02:00
daro-maj 77e1390686 test(trading): fix capsule place order test (#4286) 2023-07-12 08:54:40 +01:00
57 changed files with 1601 additions and 377 deletions
+31 -8
View File
@@ -5,10 +5,7 @@ on:
branches:
- release/*
- develop
- main
# uncomment pull_request and comment pull_request_target to test CI changes against feature branch not target branch (develop)
# pull_request:
pull_request_target:
pull_request:
types:
- opened
- ready_for_review
@@ -49,7 +46,7 @@ jobs:
lint-pr-title:
needs: node-modules
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
name: Verify PR title
uses: ./.github/workflows/lint-pr.yml
secrets: inherit
@@ -110,6 +107,7 @@ jobs:
echo "NX_HEAD: ${{ env.NX_HEAD }}"
echo "Affected: ${affected}"
echo "Branch slug: ${branch_slug}"
echo "Current ref: ${{ github.ref }}"
echo ">>>> eof debug"
projects_array=()
@@ -180,6 +178,31 @@ jobs:
fi
fi
# if branch starts with release/ and ends with trading / governance or explorer - overwrite the array of affected projects with fixed single application
if [[ "${{ github.ref }}" == *release* ]]; then
echo ">> This is a relase branch"
case "${{ github.ref }}" in
*trading)
echo ">> Only trading will be deployed"
projects_array=(trading)
projects_e2e_array=(trading)
;;
*governance)
echo ">> Only governance will be deployed"
projects_array=(governance)
projects_e2e_array=(governance)
;;
*explorer)
echo ">> Only explorer will be deployed"
projects_array=(explorer)
projects_e2e_array=(explorer)
;;
*)
echo ">> All apps will be deployed"
;;
esac
fi
echo "Projects: ${projects_array[@]}"
echo "Projects E2E: ${projects_e2e_array[@]}"
projects_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_array[@]}")
@@ -214,7 +237,7 @@ jobs:
publish-dist:
needs: lint-test-build
name: '(CD) publish dist'
# if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo') || github.event_name == 'push' }}
uses: ./.github/workflows/publish-dist.yml
secrets: inherit
with:
@@ -225,7 +248,7 @@ jobs:
needs:
- publish-dist
- lint-test-build
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }}
timeout-minutes: 60
name: '(CD) comment preview links'
steps:
@@ -271,7 +294,7 @@ jobs:
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
Previews:
Previews
* governance: ${{ needs.lint-test-build.outputs.preview_governance }}
* explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
* trading: ${{ needs.lint-test-build.outputs.preview_trading }}
+59
View File
@@ -0,0 +1,59 @@
name: console-test-run
on:
workflow_call:
inputs:
github-sha:
required: true
type: string
jobs:
console-test:
timeout-minutes: 5
runs-on: self-hosted-runner
steps:
- name: Checkout console test repo
uses: actions/checkout@v3
with:
repository: vegaprotocol/console-test
path: './console-test'
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10.11'
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Install dependencies
run: |
poetry install --no-root
working-directory: ./console-test
- name: load Binaries
run: |
poetry run python -m vega_sim.tools.load_binaries
working-directory: ./console-test
- name: pull console
run: |
poetry run docker pull ghcr.io/vegaprotocol/frontend/trading:${{ inputs.github-sha }}
- name: Update container_name in config.py
run: |
sed -i "s/container_name = \".*\"/container_name = \"vegaprotocol\/frontend\/trading:${{ inputs.github-sha }}\"/g" config.py
- name: install playwright
run: poetry run playwright install
working-directory: ./console-test
- name: run tests
run: poetry run pytest --numprocesses auto
working-directory: ./console-test
- name: Upload Playwright Trace
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-trace
path: ./traces/
retention-days: 15
+54 -21
View File
@@ -22,6 +22,39 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Init variables
run: |
echo IS_PR=false >> $GITHUB_ENV
echo IS_MAINNET_RELEASE=false >> $GITHUB_ENV
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
echo IS_S3_RELASE=false >> $GITHUB_ENV
- name: Is PR
if: ${{ github.event_name == 'pull_request' }}
run: |
echo IS_PR=true >> $GITHUB_ENV
- name: Is mainnet release
if: ${{ contains(github.ref, 'release/mainnnet') && !contains(github.ref, 'mirror') }}
run: |
echo IS_MAINNET_RELEASE=true >> $GITHUB_ENV
- name: Is testnet release
if: ${{ contains(github.ref, 'release/testnet') }}
run: |
echo IS_TESTNET_RELEASE=true >> $GITHUB_ENV
- name: Is IPFS Release
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( env.IS_MAINNET_RELEASE == 'true' || env.IS_TESTNET_RELEASE == 'true' ) }}
run: |
echo IS_IPFS_RELEASE=true >> $GITHUB_ENV
- name: Is S3 Release
if: ${{ env.IS_IPFS_RELASE == 'false' && github.event_name == 'push' }}
run: |
echo IS_S3_RELASE=true >> $GITHUB_ENV
- name: Set up QEMU
id: quemu
uses: docker/setup-qemu-action@v2
@@ -33,7 +66,7 @@ jobs:
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (ghcr)
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ env.IS_PR == 'true' }}
uses: docker/login-action@v2
with:
registry: ghcr.io
@@ -42,9 +75,8 @@ jobs:
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) }}
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
with:
# registry: registry.hub.docker.com
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -70,7 +102,8 @@ jobs:
bucketName=''
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
# remove prefixing release/ and take the first string limited by - which is supposed to be name of the environment for releasing (format: release/testnet-trading)
envName="$(echo ${{ github.ref }} | sed -e "s|refs/heads/release/||" | cut -d '-' -f 1 )"
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
envName="stagnet1"
if [[ "${{ matrix.app }}" = "multisig-signer" ]]; then
@@ -85,7 +118,7 @@ jobs:
envName="mainnet"
bucketName="ui.vega.rocks"
fi
elif [[ "${{ github.ref }}" =~ .*main$ ]]; then
elif [[ "${{ github.ref }}" =~ .*mainnet$ ]]; then
envName="mainnet"
fi
@@ -145,7 +178,7 @@ jobs:
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Image digest
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ env.IS_PR == 'true' }}
run: echo ${{ steps.docker_build.outputs.digest }}
- name: Sanity check docker image
@@ -160,7 +193,7 @@ jobs:
uses: docker/build-push-action@v3
continue-on-error: true
id: ghcr-push
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ env.IS_PR == 'true' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
@@ -175,7 +208,7 @@ jobs:
uses: docker/build-push-action@v3
continue-on-error: true
id: dockerhub-push
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) }}
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
@@ -185,7 +218,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ endsWith(github.ref, 'main') && 'mainnet' || endsWith(github.ref, 'release/testnet') && 'testnet' || '' }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
- name: Publish dist as docker image (ghcr - retry)
uses: docker/build-push-action@v3
@@ -212,13 +245,13 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ endsWith(github.ref, 'main') && 'mainnet' || endsWith(github.ref, 'release/testnet') && 'testnet' || '' }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
# bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3
uses: jakejarvis/s3-sync-action@master
# s3 releases are not happening for trading on mainnet - it's IPFS
if: ${{ github.event_name == 'push' && ( matrix.app != 'trading' || (matrix.app == 'trading' && !( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) ) ) }}
if: ${{ env.IS_S3_RELASE == 'true' }}
with:
args: --acl private --follow-symlinks --delete
env:
@@ -229,11 +262,11 @@ jobs:
SOURCE_DIR: 'dist-result'
- name: Install aws CLI
if: ${{ github.event_name == 'push' && ( matrix.app != 'trading' || (matrix.app == 'trading' && !( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) ) ) }}
if: ${{ env.IS_S3_RELASE == 'true' }}
uses: unfor19/install-aws-cli-action@master
- name: Perform cache invalidation
if: ${{ github.event_name == 'push' && ( matrix.app != 'trading' || (matrix.app == 'trading' && !( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) ) ) }}
if: ${{ env.IS_S3_RELASE == 'true' }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@@ -246,16 +279,16 @@ jobs:
- name: Add preview label
uses: actions-ecosystem/action-add-labels@v1
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ env.IS_PR == 'true' }}
with:
labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }}
- name: Trigger fleek deployment
# release to ipfs happens only on mainnet (represented by main branch) for trading
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) }}
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
run: |
if echo ${{ github.ref }} | grep -q main; then
if [[ "${{ env.IS_MAINNET_RELEASE }}" = "true" ]]; then
# display info about app
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
@@ -268,7 +301,7 @@ jobs:
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
https://api.fleek.co/graphql
elif echo ${{ github.ref }} | grep -q release/testnet; then
elif [[ "${{ env.IS_TESTNET_RELEASE }}" = "true" ]]; then
# display info about app
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
@@ -283,7 +316,7 @@ jobs:
fi
- name: Check out ipfs-redirect
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'testnet') ) }}
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
uses: actions/checkout@v3
with:
repository: 'vegaprotocol/ipfs-redirect'
@@ -292,7 +325,7 @@ jobs:
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
- name: Update interstitial page to point to the new console
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'testnet') ) }}
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
env:
GH_TOKEN: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
run: |
@@ -314,11 +347,11 @@ jobs:
git config --global user.name "vega-ci-bot"
# update CID files
if echo ${{ github.ref }} | grep -q main; then
if [[ "${{ env.IS_MAINNET_RELEASE }}" = "true" ]]; then
echo $new_hash > cidv0-mainnet.txt
echo $new_cid > cidv1-mainnet.txt
git add cidv0-mainnet.txt cidv1-mainnet.txt
elif echo ${{ github.ref }} | grep -q release/testnet; then
elif [[ "${{ env.IS_TESTNET_RELEASE }}" = "true" ]]; then
echo $new_hash > cidv0-fairground.txt
echo $new_cid > cidv1-fairground.txt
git add cidv0-fairground.txt cidv1-fairground.txt
@@ -320,8 +320,8 @@ context(
// 3001-VOTE-076
cy.getByTestId(connectToVegaWalletButton)
.should('be.visible')
.and('have.text', 'Connect Vega wallet')
.click();
.and('have.text', 'Connect Vega wallet');
cy.getByTestId(connectToVegaWalletButton).click();
cy.getByTestId('connector-jsonRpc').click();
cy.getByTestId(vegaWalletNameElement).should('be.visible');
cy.getByTestId(connectToVegaWalletButton).should('not.exist');
@@ -161,7 +161,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
cy.getByTestId('menu-drawer').should('be.visible');
});
it.skip('should have link for proposal page', function () {
it('should have link for proposal page', function () {
cy.getByTestId('menu-drawer').within(() => {
cy.get('[href="/proposals"]')
.should('exist')
@@ -1,13 +1,12 @@
/// <reference types="cypress" />
import {
navigateTo,
navigation,
turnTelemetryOff,
waitForSpinner,
} from '../../support/common.functions';
import {
enterUniqueFreeFormProposalBody,
createTenDigitUnixTimeStampForSpecifiedDays,
enterRawProposalBody,
goToMakeNewProposal,
governanceProposalType,
} from '../../support/governance.functions';
@@ -47,12 +46,11 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
.and('contain.text', 'USDC (fake)');
});
it.skip('Unable to submit proposal with public key', function () {
it('Unable to submit proposal with public key', function () {
const expectedErrorTxt = `You are connected in a view only state for public key: ${vegaWalletPubKey}. In order to send transactions you must connect to a real wallet.`;
navigateTo(navigation.proposals);
goToMakeNewProposal(governanceProposalType.FREEFORM);
enterUniqueFreeFormProposalBody('50', 'pub key proposal test');
goToMakeNewProposal(governanceProposalType.RAW);
enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8));
cy.getByTestId('dialog-content')
.first()
.within(() => {
@@ -78,7 +78,7 @@ context(
cy.getByTestId('connector-jsonRpc')
.should('be.visible')
.and('have.text', 'Connect Vega wallet');
cy.getByTestId('connector-hosted')
cy.getByTestId('connector-rest')
.should('be.visible')
.and('have.text', 'Hosted Fairground wallet');
});
@@ -94,7 +94,7 @@ context(
describe('when rest connector form opened', function () {
before('click hosted wallet app button', function () {
cy.getByTestId(connectorsList).within(() => {
cy.getByTestId('connector-hosted').click();
cy.getByTestId('connector-rest').click();
});
});
@@ -2,13 +2,8 @@ import { Button } from '@vegaprotocol/ui-toolkit';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
export const ConnectToVega = () => {
const { appDispatch } = useAppState();
const { t } = useTranslation();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
@@ -16,10 +11,6 @@ export const ConnectToVega = () => {
return (
<Button
onClick={() => {
appDispatch({
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY,
isOpen: true,
});
openVegaWalletDialog();
}}
data-testid="connect-to-vega-wallet-btn"
@@ -3,11 +3,6 @@ import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
interface VegaWalletContainerProps {
children: (key: string) => React.ReactElement;
}
@@ -15,7 +10,6 @@ interface VegaWalletContainerProps {
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const { appDispatch } = useAppState();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
@@ -25,10 +19,6 @@ export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
<Button
data-testid="connect-to-vega-wallet-btn"
onClick={() => {
appDispatch({
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY,
isOpen: true,
});
openVegaWalletDialog();
}}
>
@@ -13,12 +13,6 @@ export const VegaWalletDialogs = () => {
<>
<VegaConnectDialog
connectors={Connectors}
onChangeOpen={(open) =>
appDispatch({
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY,
isOpen: open,
})
}
riskMessage={<RiskMessage />}
/>
@@ -71,7 +71,6 @@ export const VegaWallet = () => {
const VegaWalletNotConnected = () => {
const { t } = useTranslation();
const { appDispatch } = useAppState();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
@@ -79,10 +78,6 @@ const VegaWalletNotConnected = () => {
<>
<Button
onClick={() => {
appDispatch({
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY,
isOpen: true,
});
openVegaWalletDialog();
}}
fill={true}
@@ -28,9 +28,6 @@ export interface AppState {
/** Total number of VEGA Tokens, both vesting and unlocked, associated for staking */
totalAssociated: BigNumber;
/** Whether or not the connect to VEGA wallet overlay is open */
vegaWalletOverlay: boolean;
/** Whether or not the manage VEGA wallet overlay is open */
vegaWalletManageOverlay: boolean;
@@ -52,9 +49,7 @@ export enum AppStateActionType {
SET_TOKEN,
SET_ALLOWANCE,
REFRESH_BALANCES,
SET_VEGA_WALLET_OVERLAY,
SET_VEGA_WALLET_MANAGE_OVERLAY,
SET_DRAWER,
REFRESH_ASSOCIATED_BALANCES,
SET_ASSOCIATION_BREAKDOWN,
SET_TRANSACTION_OVERLAY,
@@ -69,18 +64,10 @@ export type AppStateAction =
totalSupply: BigNumber;
totalAssociated: BigNumber;
}
| {
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY;
isOpen: boolean;
}
| {
type: AppStateActionType.SET_VEGA_WALLET_MANAGE_OVERLAY;
isOpen: boolean;
}
| {
type: AppStateActionType.SET_DRAWER;
isOpen: boolean;
}
| {
type: AppStateActionType.SET_TRANSACTION_OVERLAY;
isOpen: boolean;
@@ -14,7 +14,6 @@ const initialAppState: AppState = {
totalAssociated: new BigNumber(0),
decimals: 0,
totalSupply: new BigNumber(0),
vegaWalletOverlay: false,
vegaWalletManageOverlay: false,
transactionOverlay: false,
bannerMessage: '',
@@ -31,23 +30,10 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
totalAssociated: action.totalAssociated,
};
}
case AppStateActionType.SET_VEGA_WALLET_OVERLAY: {
return {
...state,
vegaWalletOverlay: action.isOpen,
};
}
case AppStateActionType.SET_VEGA_WALLET_MANAGE_OVERLAY: {
return {
...state,
vegaWalletManageOverlay: action.isOpen,
vegaWalletOverlay: action.isOpen ? false : state.vegaWalletOverlay,
};
}
case AppStateActionType.SET_DRAWER: {
return {
...state,
vegaWalletOverlay: false,
};
}
case AppStateActionType.SET_TRANSACTION_OVERLAY: {
@@ -2,15 +2,18 @@ import {
RestConnector,
JsonRpcConnector,
ViewConnector,
InjectedConnector,
} from '@vegaprotocol/wallet';
const urlParams = new URLSearchParams(window.location.search);
export const injected = new InjectedConnector();
export const rest = new RestConnector();
export const jsonRpc = new JsonRpcConnector();
export const view = new ViewConnector(urlParams.get('address'));
export const Connectors = {
injected,
rest,
jsonRpc,
view,
@@ -10,10 +10,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { addDecimal, toBigNum } from '@vegaprotocol/utils';
import { ProposalState, VoteValue } from '@vegaprotocol/types';
import {
AppStateActionType,
useAppState,
} from '../../../../contexts/app-state/app-state-context';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import { DATE_FORMAT_LONG } from '../../../../lib/date-formats';
import { VoteState } from './use-user-vote';
@@ -73,7 +70,6 @@ export const VoteButtons = ({
dialog: Dialog,
}: VoteButtonsProps) => {
const { t } = useTranslation();
const { appDispatch } = useAppState();
const { pubKey } = useVegaWallet();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
@@ -98,10 +94,6 @@ export const VoteButtons = ({
<div data-testid="connect-wallet">
<ButtonLink
onClick={() => {
appDispatch({
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY,
isOpen: true,
});
openVegaWalletDialog();
}}
>
@@ -142,7 +134,6 @@ export const VoteButtons = ({
minVoterBalance,
spamProtectionMinTokens,
t,
appDispatch,
openVegaWalletDialog,
]);
@@ -14,7 +14,6 @@ const mockAppState: AppState = {
totalAssociated: new BigNumber('50063005'),
decimals: 18,
totalSupply: mockTotalSupply,
vegaWalletOverlay: false,
vegaWalletManageOverlay: false,
transactionOverlay: false,
bannerMessage: '',
@@ -2,14 +2,9 @@ import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Button } from '@vegaprotocol/ui-toolkit';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
import { SubHeading } from '../../components/heading';
export const ConnectToSeeRewards = () => {
const { appDispatch } = useAppState();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
@@ -26,10 +21,6 @@ export const ConnectToSeeRewards = () => {
<Button
data-testid="connect-to-vega-wallet-btn"
onClick={() => {
appDispatch({
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY,
isOpen: true,
});
openVegaWalletDialog();
}}
>
@@ -2,7 +2,6 @@ import { removeDecimal } from '@vegaprotocol/cypress';
import * as Schema from '@vegaprotocol/types';
import {
OrderStatusMapping,
OrderTimeInForceMapping,
OrderTypeMapping,
Side,
} from '@vegaprotocol/types';
@@ -17,7 +16,6 @@ const orderStatus = 'status';
const orderRemaining = 'remaining';
const orderPrice = 'price';
const orderTimeInForce = 'timeInForce';
const orderCreatedAt = 'createdAt';
const orderUpdatedAt = 'updatedAt';
const assetSelectField = 'select[name="asset"]';
const amountField = 'input[name="amount"]';
@@ -260,10 +258,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
OrderStatusMapping.STATUS_ACTIVE
);
cy.get(`[col-id='${orderRemaining}']`).should(
'contain.text',
`0.00/${order.size}`
);
cy.get(`[col-id='${orderRemaining}']`).should('contain.text', '0.00');
cy.get(`[col-id='${orderPrice}']`).then(($price) => {
expect(parseFloat($price.text())).to.equal(parseFloat(order.price));
@@ -271,10 +266,10 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.get(`[col-id='${orderTimeInForce}']`).should(
'contain.text',
OrderTimeInForceMapping[order.timeInForce]
'GTC'
);
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderCreatedAt);
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderUpdatedAt);
});
});
});
@@ -68,14 +68,15 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
validateMarketDataRow(0, 'Name', 'BTCUSD Monthly (30 Jun 2022)');
validateMarketDataRow(1, 'Market ID', 'market-0');
validateMarketDataRow(2, 'Parent Market ID', 'market-1');
validateMarketDataRow(
2,
3,
'Trading Mode',
MarketTradingModeMapping.TRADING_MODE_CONTINUOUS
);
validateMarketDataRow(3, 'Market Decimal Places', '5');
validateMarketDataRow(4, 'Position Decimal Places', '0');
validateMarketDataRow(5, 'Settlement Asset Decimal Places', '5');
validateMarketDataRow(4, 'Market Decimal Places', '5');
validateMarketDataRow(5, 'Position Decimal Places', '0');
validateMarketDataRow(6, 'Settlement Asset Decimal Places', '5');
});
it('instrument displayed', () => {
@@ -63,7 +63,7 @@ describe(
cy.contains('Hosted Fairground wallet');
cy.getByTestId('connectors-list')
.find('[data-testid="connector-hosted"]')
.find('[data-testid="connector-rest"]')
.click();
cy.getByTestId(form).find('#wallet').click().type('user');
cy.getByTestId(form).find('#passphrase').click().type('pass');
@@ -89,7 +89,7 @@ describe(
);
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-hosted"]')
.find('[data-testid="connector-rest"]')
.click();
cy.getByTestId(form).find('#wallet').click().type('invalid name');
cy.getByTestId(form).find('#passphrase').click().type('invalid password');
@@ -100,7 +100,7 @@ describe(
it('doesnt connect with empty fields', () => {
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-hosted"]')
.find('[data-testid="connector-rest"]')
.click();
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
@@ -27,6 +27,7 @@ import {
import { TradingViews } from './trade-views';
import { MarketSelector } from './market-selector';
import { HeaderStats } from './header-stats';
import { MarketSuccessorBanner } from '../../components/market-banner';
interface TradeGridProps {
market: Market | null;
@@ -317,6 +318,7 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
<HeaderStats market={market} />
</div>
<div className="col-span-2">
<MarketSuccessorBanner market={market} />
<OracleBanner marketId={market?.id || ''} />
</div>
{sidebarOpen && (
@@ -21,6 +21,7 @@ import { HeaderStats } from './header-stats';
import * as DialogPrimitives from '@radix-ui/react-dialog';
import { HeaderTitle } from '../../components/header';
import { MarketSelector } from './market-selector';
import { MarketSuccessorBanner } from '../../components/market-banner';
interface TradePanelsProps {
market: Market | null;
@@ -92,6 +93,7 @@ export const TradePanels = ({
<HeaderStats market={market} />
</div>
<div>
<MarketSuccessorBanner market={market} />
<OracleBanner marketId={market?.id || ''} />
</div>
<div className="h-full">
@@ -0,0 +1 @@
export * from './market-successor-banner';
@@ -0,0 +1,188 @@
import { render, screen } from '@testing-library/react';
import { MockedProvider } from '@apollo/react-testing';
import * as dataProviders from '@vegaprotocol/data-provider';
import { MarketSuccessorBanner } from './market-successor-banner';
import * as Types from '@vegaprotocol/types';
import * as allUtils from '@vegaprotocol/utils';
import type { Market } from '@vegaprotocol/markets';
import type { PartialDeep } from 'type-fest';
const market = {
id: 'marketId',
tradableInstrument: {
instrument: {
metadata: {
tags: [],
},
},
},
marketTimestamps: {
close: null,
},
successorMarketID: 'successorMarketID',
} as unknown as Market;
let mockDataSuccessorMarket: PartialDeep<Market> | null = null;
jest.mock('@vegaprotocol/data-provider', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useDataProvider: jest.fn().mockImplementation((args) => {
if (args.skip) {
return {
data: null,
error: null,
};
}
return {
data: mockDataSuccessorMarket,
error: null,
};
}),
}));
jest.mock('@vegaprotocol/utils', () => ({
...jest.requireActual('@vegaprotocol/utils'),
getMarketExpiryDate: jest.fn(),
}));
let mockCandles = {};
jest.mock('@vegaprotocol/markets', () => ({
...jest.requireActual('@vegaprotocol/markets'),
useCandles: () => mockCandles,
}));
describe('MarketSuccessorBanner', () => {
beforeEach(() => {
jest.clearAllMocks();
mockDataSuccessorMarket = {
id: 'successorMarketID',
state: Types.MarketState.STATE_ACTIVE,
tradingMode: Types.MarketTradingMode.TRADING_MODE_CONTINUOUS,
tradableInstrument: {
instrument: {
name: 'Successor Market Name',
},
},
};
});
describe('should be hidden', () => {
it('when no market', () => {
const { container } = render(<MarketSuccessorBanner market={null} />, {
wrapper: MockedProvider,
});
expect(container).toBeEmptyDOMElement();
});
it('when no successorMarketID', () => {
const amendedMarket = {
...market,
successorMarketID: null,
};
const { container } = render(
<MarketSuccessorBanner market={amendedMarket} />,
{
wrapper: MockedProvider,
}
);
expect(container).toBeEmptyDOMElement();
expect(dataProviders.useDataProvider).lastCalledWith(
expect.objectContaining({ skip: true })
);
});
it('no successor market data', () => {
mockDataSuccessorMarket = null;
const { container } = render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(container).toBeEmptyDOMElement();
expect(dataProviders.useDataProvider).lastCalledWith(
expect.objectContaining({
variables: { marketId: 'successorMarketID' },
skip: false,
})
);
});
it('successor market not in continuous mode', () => {
mockDataSuccessorMarket = {
...mockDataSuccessorMarket,
tradingMode: Types.MarketTradingMode.TRADING_MODE_NO_TRADING,
};
const { container } = render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(container).toBeEmptyDOMElement();
expect(dataProviders.useDataProvider).lastCalledWith(
expect.objectContaining({
variables: { marketId: 'successorMarketID' },
skip: false,
})
);
expect(allUtils.getMarketExpiryDate).toHaveBeenCalled();
});
it('successor market is not active', () => {
mockDataSuccessorMarket = {
...mockDataSuccessorMarket,
state: Types.MarketState.STATE_PENDING,
};
const { container } = render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(container).toBeEmptyDOMElement();
expect(dataProviders.useDataProvider).lastCalledWith(
expect.objectContaining({
variables: { marketId: 'successorMarketID' },
skip: false,
})
);
expect(allUtils.getMarketExpiryDate).toHaveBeenCalled();
});
});
describe('should be displayed', () => {
it('should be rendered', () => {
render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(
screen.getByText('This market has been succeeded')
).toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'Successor Market Name' })
).toHaveAttribute('href', '/#/markets/successorMarketID');
});
it('should display optionally successor volume', () => {
mockDataSuccessorMarket = {
...mockDataSuccessorMarket,
positionDecimalPlaces: 3,
};
mockCandles = {
oneDayCandles: [
{ volume: 123 },
{ volume: 456 },
{ volume: 789 },
{ volume: 99999 },
],
};
render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(screen.getByText('has 101.367 24h vol.')).toBeInTheDocument();
});
it('should display optionally duration', () => {
jest
.spyOn(allUtils, 'getMarketExpiryDate')
.mockReturnValue(
new Date(Date.now() + 24 * 60 * 60 * 1000 + 60 * 1000)
);
render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(
screen.getByText(/^This market expires in 1 day/)
).toBeInTheDocument();
});
});
});
@@ -0,0 +1,115 @@
import { useState } from 'react';
import { isBefore, formatDuration, intervalToDuration } from 'date-fns';
import { useDataProvider } from '@vegaprotocol/data-provider';
import type { Market } from '@vegaprotocol/markets';
import {
calcCandleVolume,
marketProvider,
useCandles,
} from '@vegaprotocol/markets';
import {
ExternalLink,
Intent,
NotificationBanner,
} from '@vegaprotocol/ui-toolkit';
import {
addDecimalsFormatNumber,
getMarketExpiryDate,
isNumeric,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import * as Types from '@vegaprotocol/types';
const getExpiryDate = (tags: string[], close?: string): Date | null => {
const expiryDate = getMarketExpiryDate(tags);
return expiryDate || (close && new Date(close)) || null;
};
export const MarketSuccessorBanner = ({
market,
}: {
market: Market | null;
}) => {
const { data: successorData } = useDataProvider({
dataProvider: marketProvider,
variables: {
marketId: market?.successorMarketID || '',
},
skip: !market?.successorMarketID,
});
const [visible, setVisible] = useState(true);
const expiry = market
? getExpiryDate(
market.tradableInstrument.instrument.metadata.tags || [],
market.marketTimestamps.close
)
: null;
const duration =
expiry && isBefore(new Date(), expiry)
? intervalToDuration({ start: new Date(), end: expiry })
: null;
const isInContinuesMode =
successorData?.state === Types.MarketState.STATE_ACTIVE &&
successorData?.tradingMode ===
Types.MarketTradingMode.TRADING_MODE_CONTINUOUS;
const { oneDayCandles } = useCandles({
marketId: successorData?.id,
});
const candleVolume = oneDayCandles?.length
? calcCandleVolume(oneDayCandles)
: null;
const successorVolume =
candleVolume && isNumeric(successorData?.positionDecimalPlaces)
? addDecimalsFormatNumber(
candleVolume,
successorData?.positionDecimalPlaces as number
)
: null;
if (isInContinuesMode && visible) {
return (
<NotificationBanner
intent={Intent.Primary}
onClose={() => {
setVisible(false);
}}
>
<div className="uppercase mb-1">
{t('This market has been succeeded')}
</div>
<div>
{duration && (
<span>
{t('This market expires in %s.', [
formatDuration(duration, {
format: [
'years',
'months',
'weeks',
'days',
'hours',
'minutes',
],
}),
])}
</span>
)}{' '}
{t('The successor market')}{' '}
<ExternalLink href={`/#/markets/${successorData?.id}`}>
{successorData?.tradableInstrument.instrument.name}
</ExternalLink>
{successorVolume && (
<span> {t('has %s 24h vol.', [successorVolume])}</span>
)}
</div>
</NotificationBanner>
);
}
return null;
};
@@ -190,6 +190,9 @@ export const VegaWalletConnectButton = () => {
>
<DropdownMenuContent
onInteractOutside={() => setDropdownOpen(false)}
sideOffset={20}
side="bottom"
align="end"
>
<div className="min-w-[340px]" data-testid="keypair-list">
<DropdownMenuRadioGroup
+3
View File
@@ -2,10 +2,12 @@ import {
RestConnector,
JsonRpcConnector,
ViewConnector,
InjectedConnector,
} from '@vegaprotocol/wallet';
export const rest = new RestConnector();
export const jsonRpc = new JsonRpcConnector();
export const injected = new InjectedConnector();
let view: ViewConnector;
if (typeof window !== 'undefined') {
@@ -16,6 +18,7 @@ if (typeof window !== 'undefined') {
}
export const Connectors = {
injected,
rest,
jsonRpc,
view,
+1 -1
View File
@@ -180,4 +180,4 @@ export function useLiquidityProviderFeeShareLazyQuery(baseOptions?: Apollo.LazyQ
}
export type LiquidityProviderFeeShareQueryHookResult = ReturnType<typeof useLiquidityProviderFeeShareQuery>;
export type LiquidityProviderFeeShareLazyQueryHookResult = ReturnType<typeof useLiquidityProviderFeeShareLazyQuery>;
export type LiquidityProviderFeeShareQueryResult = Apollo.QueryResult<LiquidityProviderFeeShareQuery, LiquidityProviderFeeShareQueryVariables>;
export type LiquidityProviderFeeShareQueryResult = Apollo.QueryResult<LiquidityProviderFeeShareQuery, LiquidityProviderFeeShareQueryVariables>;
+3 -2
View File
@@ -7,12 +7,12 @@ export type DataSourceFilterFragment = { __typename?: 'Filter', key: { __typenam
export type DataSourceSpecFragment = { __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 } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } };
export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, 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 } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, 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 } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } };
export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, successorMarketID?: string | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, 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 } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, 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 } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } };
export type MarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, 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 } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, 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 } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null };
export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, successorMarketID?: string | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, 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 } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, 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 } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null };
export const DataSourceFilterFragmentDoc = gql`
fragment DataSourceFilter on Filter {
@@ -104,6 +104,7 @@ export const MarketFieldsFragmentDoc = gql`
open
close
}
successorMarketID
}
${DataSourceSpecFragmentDoc}`;
export const MarketsDocument = gql`
@@ -142,5 +142,6 @@ query MarketInfo($marketId: ID!) {
}
}
}
parentMarketID
}
}
@@ -10,7 +10,7 @@ export type MarketInfoQueryVariables = Types.Exact<{
}>;
export type MarketInfoQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, lpPriceRange: string, proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string } } } | null> | null } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, riskFactors?: { __typename?: 'RiskFactor', market: string, short: string, long: string } | null, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, 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?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, 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?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null } } | null };
export type MarketInfoQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, lpPriceRange: string, parentMarketID?: string | null, proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string } } } | null> | null } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, riskFactors?: { __typename?: 'RiskFactor', market: string, short: string, long: string } | null, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, 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?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, 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?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null } } | null };
export const DataSourceFragmentDoc = gql`
fragment DataSource on DataSourceDefinition {
@@ -158,6 +158,7 @@ export const MarketInfoDocument = gql`
}
}
}
parentMarketID
}
}
${DataSourceFragmentDoc}`;
@@ -144,6 +144,7 @@ export const KeyDetailsInfoPanel = ({ market }: MarketInfoProps) => {
data={{
name: market.tradableInstrument.instrument.name,
marketID: market.id,
parentMarketID: market.parentMarketID,
tradingMode:
market.tradingMode && MarketTradingModeMapping[market.tradingMode],
marketDecimalPlaces: market.decimalPlaces,
@@ -191,6 +191,7 @@ export const marketInfoQuery = (
},
},
},
parentMarketID: 'market-1',
},
};
@@ -102,4 +102,5 @@ export const tooltipMapping: Record<string, ReactNode> = {
`The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.`
),
suppliedStake: t('The current amount of liquidity supplied for this market.'),
parentMarketID: t('The ID of the market this market succeeds'),
};
+1
View File
@@ -85,6 +85,7 @@ fragment MarketFields on Market {
open
close
}
successorMarketID
}
query Markets {
+320 -8
View File
@@ -356,6 +356,13 @@ export enum BusEventType {
Withdrawal = 'Withdrawal'
}
/** Allows for cancellation of an existing governance transfer */
export type CancelTransfer = {
__typename?: 'CancelTransfer';
/** The governance transfer to cancel */
transferId: Scalars['ID'];
};
/** Candle stick representation of trading */
export type Candle = {
__typename?: 'Candle';
@@ -367,6 +374,8 @@ export type Candle = {
lastUpdateInPeriod: Scalars['Timestamp'];
/** Low price (uint64) */
low: Scalars['String'];
/** Total notional value of trades (uint64) */
notional: Scalars['String'];
/** Open price (uint64) */
open: Scalars['String'];
/** RFC3339Nano formatted date and time for the candle start time */
@@ -1123,6 +1132,17 @@ export type FutureProduct = {
settlementAsset: Asset;
};
export type GovernanceTransferKind = OneOffGovernanceTransfer | RecurringGovernanceTransfer;
export enum GovernanceTransferType {
/** Transfers the specified amount or does not transfer anything */
GOVERNANCE_TRANSFER_TYPE_ALL_OR_NOTHING = 'GOVERNANCE_TRANSFER_TYPE_ALL_OR_NOTHING',
/** Transfers the specified amount or the max allowable amount if this is less than the specified amount */
GOVERNANCE_TRANSFER_TYPE_BEST_EFFORT = 'GOVERNANCE_TRANSFER_TYPE_BEST_EFFORT',
/** Default value, always invalid */
GOVERNANCE_TRANSFER_TYPE_UNSPECIFIED = 'GOVERNANCE_TRANSFER_TYPE_UNSPECIFIED'
}
/** A segment of data node history */
export type HistorySegment = {
__typename?: 'HistorySegment';
@@ -1134,6 +1154,17 @@ export type HistorySegment = {
toHeight: Scalars['Int'];
};
/** Details of the iceberg order */
export type IcebergOrder = {
__typename?: 'IcebergOrder';
/** If the visible size of the order falls below this value, it will be replenished back to the peak size using the reserved amount */
minimumVisibleSize: Scalars['String'];
/** Size of the order that will be made visible if the iceberg order is replenished after trading */
peakSize: Scalars['String'];
/** Size of the order that is reserved and used to restore the iceberg's peak when it is refreshed */
reservedRemaining: Scalars['String'];
};
/** Describes something that can be traded on Vega */
export type Instrument = {
__typename?: 'Instrument';
@@ -1305,10 +1336,12 @@ export type LiquidityProviderFeeShare = {
averageEntryValuation: Scalars['String'];
/** The average liquidity score */
averageScore: Scalars['String'];
/** The share owned by this liquidity provider (float) */
/** The share owned by this liquidity provider */
equityLikeShare: Scalars['String'];
/** The liquidity provider party ID */
party: Party;
/** The virtual stake for this liquidity provider */
virtualStake: Scalars['String'];
};
/** The command to be sent to the chain for a liquidity provision submission */
@@ -1323,7 +1356,7 @@ export type LiquidityProvision = {
/** Nominated liquidity fee factor, which is an input to the calculation of liquidity fees on the market, as per setting fees and rewarding liquidity providers. */
fee: Scalars['String'];
/** Unique identifier for the order (set by the system after consensus) */
id?: Maybe<Scalars['ID']>;
id: Scalars['ID'];
/** Market for the order */
market: Market;
/** The party making this commitment */
@@ -1372,7 +1405,7 @@ export type LiquidityProvisionUpdate = {
/** Nominated liquidity fee factor, which is an input to the calculation of liquidity fees on the market, as per setting fees and rewarding liquidity providers. */
fee: Scalars['String'];
/** Unique identifier for the order (set by the system after consensus) */
id?: Maybe<Scalars['ID']>;
id: Scalars['ID'];
/** Market for the order */
marketID: Scalars['ID'];
/** The party making this commitment */
@@ -1546,6 +1579,8 @@ export type Market = {
fees: Fees;
/** Market ID */
id: Scalars['ID'];
/** Optional: When a successor market is created, a fraction of the parent market's insurance pool can be transferred to the successor market */
insurancePoolFraction?: Maybe<Scalars['String']>;
/** Linear slippage factor is used to cap the slippage component of maintainence margin - it is applied to the slippage volume */
linearSlippageFactor: Scalars['String'];
/** Liquidity monitoring parameters for the market */
@@ -1563,6 +1598,11 @@ export type Market = {
openingAuction: AuctionDuration;
/** Orders on a market */
ordersConnection?: Maybe<OrderConnection>;
/**
* Optional: Parent market ID. A market can be a successor to another market. If this market is a successor to a previous market,
* this field will be populated with the ID of the previous market.
*/
parentMarketID?: Maybe<Scalars['ID']>;
/**
* The number of decimal places that an integer must be shifted in order to get a correct size (uint64).
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
@@ -1580,6 +1620,8 @@ export type Market = {
riskFactors?: Maybe<RiskFactor>;
/** Current state of the market */
state: MarketState;
/** Optional: Market ID of the successor to this market if one exists */
successorMarketID?: Maybe<Scalars['ID']>;
/** An instance of, or reference to, a tradable instrument. */
tradableInstrument: TradableInstrument;
/** @deprecated Simplify and consolidate trades query and remove nesting. Use trades query instead */
@@ -1672,12 +1714,16 @@ export type MarketData = {
indicativePrice: Scalars['String'];
/** Indicative volume if the auction ended now, 0 if not in auction mode */
indicativeVolume: Scalars['String'];
/** The last traded price (an unsigned integer) */
lastTradedPrice: Scalars['String'];
/** The equity like share of liquidity fee for each liquidity provider */
liquidityProviderFeeShare?: Maybe<Array<LiquidityProviderFeeShare>>;
/** The mark price (an unsigned integer) */
markPrice: Scalars['String'];
/** Market of the associated mark price */
market: Market;
/** The market growth factor for the last market time window */
marketGrowth: Scalars['String'];
/** Current state of the market */
marketState: MarketState;
/** What mode the market is in (auction, continuous, etc) */
@@ -1775,7 +1821,7 @@ export type MarketDepthUpdate = {
sequenceNumber: Scalars['String'];
};
/** Edge type containing the order and cursor information returned by a OrderConnection */
/** Edge type containing the market and cursor information returned by a MarketConnection */
export type MarketEdge = {
__typename?: 'MarketEdge';
/** The cursor for this market */
@@ -1932,7 +1978,7 @@ export type NewMarket = {
decimalPlaces: Scalars['Int'];
/** New market instrument configuration */
instrument: InstrumentConfiguration;
/** Linear slippage factor is used to cap the slippage component of maintainence margin - it is applied to the slippage volume */
/** Linear slippage factor is used to cap the slippage component of maintenance margin - it is applied to the slippage volume */
linearSlippageFactor: Scalars['String'];
/** Liquidity monitoring parameters */
liquidityMonitoringParameters: LiquidityMonitoringParameters;
@@ -1944,10 +1990,34 @@ export type NewMarket = {
positionDecimalPlaces: Scalars['Int'];
/** Price monitoring parameters */
priceMonitoringParameters: PriceMonitoringParameters;
/** Quadratic slippage factor is used to cap the slippage component of maintainence margin - it is applied to the square of the slippage volume */
/** Quadratic slippage factor is used to cap the slippage component of maintenance margin - it is applied to the square of the slippage volume */
quadraticSlippageFactor: Scalars['String'];
/** New market risk configuration */
riskParameters: RiskModel;
/** Successor market configuration. If this proposed market is meant to succeed a given market, then this needs to be set. */
successorConfiguration?: Maybe<SuccessorConfiguration>;
};
export type NewTransfer = {
__typename?: 'NewTransfer';
/** The maximum amount to be transferred */
amount: Scalars['String'];
/** The asset to transfer */
asset: Asset;
/** The destination account */
destination: Scalars['String'];
/** The type of destination account */
destinationType: AccountType;
/** The fraction of the balance to be transferred */
fraction_of_balance: Scalars['String'];
/** The type of governance transfer being made, i.e. a one-off or recurring transfer */
kind: GovernanceTransferKind;
/** The source account */
source: Scalars['String'];
/** The type of source account */
sourceType: AccountType;
/** The type of the governance transfer */
transferType: GovernanceTransferType;
};
/** Information available for a node */
@@ -2161,10 +2231,14 @@ export type ObservableMarketData = {
indicativePrice: Scalars['String'];
/** Indicative volume if the auction ended now, 0 if not in auction mode */
indicativeVolume: Scalars['String'];
/** The last traded price (an unsigned integer) */
lastTradedPrice: Scalars['String'];
/** The equity like share of liquidity fee for each liquidity provider */
liquidityProviderFeeShare?: Maybe<Array<ObservableLiquidityProviderFeeShare>>;
/** The mark price (an unsigned integer) */
markPrice: Scalars['String'];
/** The market growth factor for the last market time window */
marketGrowth: Scalars['String'];
/** Market ID of the associated mark price */
marketId: Scalars['ID'];
/** Current state of the market */
@@ -2229,6 +2303,13 @@ export type ObservableMarketDepthUpdate = {
sequenceNumber: Scalars['String'];
};
/** The specific details for a one-off governance transfer */
export type OneOffGovernanceTransfer = {
__typename?: 'OneOffGovernanceTransfer';
/** An optional time when the transfer should be delivered */
deliverOn?: Maybe<Scalars['Timestamp']>;
};
/** The specific details for a one-off transfer */
export type OneOffTransfer = {
__typename?: 'OneOffTransfer';
@@ -2293,6 +2374,8 @@ export type Order = {
createdAt: Scalars['Timestamp'];
/** Expiration time of this order (ISO-8601 RFC3339+Nano formatted date) */
expiresAt?: Maybe<Scalars['Timestamp']>;
/** Details of an iceberg order */
icebergOrder?: Maybe<IcebergOrder>;
/** Hash of the order data */
id: Scalars['ID'];
/** The liquidity provision this order was created from */
@@ -2537,6 +2620,35 @@ export enum OrderStatus {
STATUS_STOPPED = 'STATUS_STOPPED'
}
/** Details of the order that will be submitted when the stop order is triggered. */
export type OrderSubmission = {
__typename?: 'OrderSubmission';
/** Expiration time of this order (ISO-8601 RFC3339+Nano formatted date) */
expiresAt: Scalars['Timestamp'];
/** Details of an iceberg order */
icebergOrder?: Maybe<IcebergOrder>;
/** Market the order is for. */
marketId: Scalars['ID'];
/** PeggedOrder contains the details about a pegged order */
peggedOrder?: Maybe<PeggedOrder>;
/** Is this a post only order */
postOnly?: Maybe<Scalars['Boolean']>;
/** The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64) */
price: Scalars['String'];
/** Is this a reduce only order */
reduceOnly?: Maybe<Scalars['Boolean']>;
/** The external reference (if available) for the order */
reference?: Maybe<Scalars['String']>;
/** Whether the order is to buy or sell */
side: Side;
/** Total number of units that may be bought or sold (immutable) (uint64) */
size: Scalars['String'];
/** The timeInForce of order (determines how and if it executes, and whether it persists on the book) */
timeInForce: OrderTimeInForce;
/** The order type */
type: OrderType;
};
/** Valid order types, these determine what happens when an order is added to the book */
export enum OrderTimeInForce {
/** Fill or Kill: The order either trades completely (remainingSize == 0 after adding) or not at all, does not remain on the book if it doesn't trade */
@@ -2576,6 +2688,8 @@ export type OrderUpdate = {
createdAt: Scalars['Timestamp'];
/** Expiration time of this order (ISO-8601 RFC3339+Nano formatted date) */
expiresAt?: Maybe<Scalars['Timestamp']>;
/** Details of an iceberg order */
icebergOrder?: Maybe<IcebergOrder>;
/** Hash of the order data */
id: Scalars['ID'];
/** The liquidity provision this order was created from */
@@ -3089,7 +3203,7 @@ export type Proposal = {
votes: ProposalVotes;
};
export type ProposalChange = NewAsset | NewFreeform | NewMarket | UpdateAsset | UpdateMarket | UpdateNetworkParameter;
export type ProposalChange = CancelTransfer | NewAsset | NewFreeform | NewMarket | NewTransfer | UpdateAsset | UpdateMarket | UpdateNetworkParameter;
export type ProposalDetail = {
__typename?: 'ProposalDetail';
@@ -3160,6 +3274,12 @@ export enum ProposalRejectionReason {
PROPOSAL_ERROR_ENACT_TIME_TOO_SOON = 'PROPOSAL_ERROR_ENACT_TIME_TOO_SOON',
/** The ERC-20 address specified by this proposal is already in use by another asset */
PROPOSAL_ERROR_ERC20_ADDRESS_ALREADY_IN_USE = 'PROPOSAL_ERROR_ERC20_ADDRESS_ALREADY_IN_USE',
/** The proposal for cancellation of an active governance transfer has failed */
PROPOSAL_ERROR_GOVERNANCE_CANCEL_TRANSFER_PROPOSAL_INVALID = 'PROPOSAL_ERROR_GOVERNANCE_CANCEL_TRANSFER_PROPOSAL_INVALID',
/** The governance transfer proposal has failed */
PROPOSAL_ERROR_GOVERNANCE_TRANSFER_PROPOSAL_FAILED = 'PROPOSAL_ERROR_GOVERNANCE_TRANSFER_PROPOSAL_FAILED',
/** The governance transfer proposal is invalid */
PROPOSAL_ERROR_GOVERNANCE_TRANSFER_PROPOSAL_INVALID = 'PROPOSAL_ERROR_GOVERNANCE_TRANSFER_PROPOSAL_INVALID',
/** Proposal terms timestamps are not compatible (Validation < Closing < Enactment) */
PROPOSAL_ERROR_INCOMPATIBLE_TIMESTAMPS = 'PROPOSAL_ERROR_INCOMPATIBLE_TIMESTAMPS',
/** The proposal is rejected because the party does not have enough equity like share in the market */
@@ -3184,6 +3304,10 @@ export enum ProposalRejectionReason {
PROPOSAL_ERROR_INVALID_RISK_PARAMETER = 'PROPOSAL_ERROR_INVALID_RISK_PARAMETER',
/** Market proposal has one or more invalid liquidity shapes */
PROPOSAL_ERROR_INVALID_SHAPE = 'PROPOSAL_ERROR_INVALID_SHAPE',
/** Validation of spot market proposal failed */
PROPOSAL_ERROR_INVALID_SPOT = 'PROPOSAL_ERROR_INVALID_SPOT',
/** Validation of successor market has failed */
PROPOSAL_ERROR_INVALID_SUCCESSOR_MARKET = 'PROPOSAL_ERROR_INVALID_SUCCESSOR_MARKET',
/** Proposal declined because the majority threshold was not reached */
PROPOSAL_ERROR_MAJORITY_THRESHOLD_NOT_REACHED = 'PROPOSAL_ERROR_MAJORITY_THRESHOLD_NOT_REACHED',
/** Market proposal is missing a liquidity commitment */
@@ -3499,6 +3623,12 @@ export type Query = {
protocolUpgradeStatus?: Maybe<ProtocolUpgradeStatus>;
/** Get statistics about the Vega node */
statistics: Statistics;
/** Get stop order by ID */
stopOrder?: Maybe<StopOrder>;
/** Get a list of stop orders. If provided, the filter will be applied to the list of stop orders to restrict the results. */
stopOrders?: Maybe<StopOrderConnection>;
/** List markets in a succession line */
successorMarkets?: Maybe<SuccessorMarketConnection>;
/** Get a list of all trades and apply any given filters to the results */
trades?: Maybe<TradeConnection>;
/** Get a list of all transfers for a public key */
@@ -3558,6 +3688,7 @@ export type QueryentitiesArgs = {
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QueryepochArgs = {
block?: InputMaybe<Scalars['String']>;
id?: InputMaybe<Scalars['ID']>;
};
@@ -3803,6 +3934,27 @@ export type QueryprotocolUpgradeProposalsArgs = {
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QuerystopOrderArgs = {
id: Scalars['ID'];
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QuerystopOrdersArgs = {
filter?: InputMaybe<StopOrderFilter>;
pagination?: InputMaybe<Pagination>;
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QuerysuccessorMarketsArgs = {
fullHistory?: InputMaybe<Scalars['Boolean']>;
marketId: Scalars['ID'];
pagination?: InputMaybe<Pagination>;
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QuerytradesArgs = {
dateRange?: InputMaybe<DateRange>;
@@ -3847,6 +3999,15 @@ export type RankingScore = {
votingPower: Scalars['String'];
};
/** The specific details for a recurring governance transfer */
export type RecurringGovernanceTransfer = {
__typename?: 'RecurringGovernanceTransfer';
/** An optional epoch at which this transfer will stop */
endEpoch?: Maybe<Scalars['Int']>;
/** The epoch at which this recurring transfer will start */
startEpoch: Scalars['Int'];
};
/** The specific details for a recurring transfer */
export type RecurringTransfer = {
__typename?: 'RecurringTransfer';
@@ -4182,6 +4343,117 @@ export type Statistics = {
vegaTime: Scalars['Timestamp'];
};
/** A stop order in Vega */
export type StopOrder = {
__typename?: 'StopOrder';
/** Time the stop order was created. */
createdAt: Scalars['Timestamp'];
/** Time at which the order will expire if an expiry time is set. */
expiresAt?: Maybe<Scalars['Timestamp']>;
/** If an expiry is set, what should the stop order do when it expires. */
expiryStrategy?: Maybe<StopOrderExpiryStrategy>;
/** Hash of the stop order data */
id: Scalars['ID'];
/** Market the stop order is for. */
marketId: Scalars['ID'];
/** If OCO (one-cancels-other) order, the ID of the associated order. */
ocoLinkId?: Maybe<Scalars['ID']>;
/** Party that submitted the stop order. */
partyId: Scalars['ID'];
/** Status of the stop order */
status: StopOrderStatus;
/** Order to submit when the stop order is triggered. */
submission: OrderSubmission;
/** Price movement that will trigger the stop order */
trigger?: Maybe<StopOrderTrigger>;
/** Direction the price is moving to trigger the stop order. */
triggerDirection: StopOrderTriggerDirection;
/** Time the stop order was last updated. */
updatedAt?: Maybe<Scalars['Timestamp']>;
};
/** Connection type for retrieving cursory-based paginated stop order information */
export type StopOrderConnection = {
__typename?: 'StopOrderConnection';
/** The stop orders in this connection */
edges?: Maybe<Array<StopOrderEdge>>;
/** The pagination information */
pageInfo?: Maybe<PageInfo>;
};
/** Edge type containing the stop order and cursor information returned by a StopOrderConnection */
export type StopOrderEdge = {
__typename?: 'StopOrderEdge';
/** The cursor for this stop order */
cursor?: Maybe<Scalars['String']>;
/** The stop order */
node?: Maybe<StopOrder>;
};
/** Valid stop order expiry strategies. The expiry strategy determines what happens to a stop order when it expires. */
export enum StopOrderExpiryStrategy {
/** The stop order will be cancelled when it expires. */
EXPIRY_STRATEGY_CANCELS = 'EXPIRY_STRATEGY_CANCELS',
/** The stop order will be submitted when the expiry time is reached. */
EXPIRY_STRATEGY_SUBMIT = 'EXPIRY_STRATEGY_SUBMIT',
/** The stop order expiry strategy has not been specified by the trader. */
EXPIRY_STRATEGY_UNSPECIFIED = 'EXPIRY_STRATEGY_UNSPECIFIED'
}
/** Filter to be applied when querying a list of stop orders. If multiple criteria are specified, e.g. parties and markets, then the filter is applied as an AND. */
export type StopOrderFilter = {
/** Date range to retrieve order from/to. Start and end time should be expressed as an integer value of nano-seconds past the Unix epoch */
dateRange?: InputMaybe<DateRange>;
/** Zero or more expiry strategies to filter by */
expiryStrategy?: InputMaybe<Array<StopOrderExpiryStrategy>>;
/** Zero or more market IDs to filter by */
markets?: InputMaybe<Array<Scalars['ID']>>;
/** Zero or more party IDs to filter by */
parties?: InputMaybe<Array<Scalars['ID']>>;
/** Zero or more order status to filter by */
status?: InputMaybe<Array<StopOrderStatus>>;
};
/** Price at which a stop order will trigger */
export type StopOrderPrice = {
__typename?: 'StopOrderPrice';
price: Scalars['String'];
};
/** Valid stop order statuses, these determine several states for a stop order that cannot be expressed with other fields in StopOrder. */
export enum StopOrderStatus {
/** Stop order has been cancelled. This could be by the trader or by the network. */
STATUS_CANCELLED = 'STATUS_CANCELLED',
/** Stop order has expired. This means the trigger conditions have not been met and the stop order has expired. */
STATUS_EXPIRED = 'STATUS_EXPIRED',
/** Stop order is pending. This means the stop order has been accepted in the network, but the trigger conditions have not been met. */
STATUS_PENDING = 'STATUS_PENDING',
/** Stop order has been rejected. This means the stop order was not accepted by the network. */
STATUS_REJECTED = 'STATUS_REJECTED',
/** Stop order has been stopped. This means the trigger conditions have been met, but the stop order was not executed, and stopped. */
STATUS_STOPPED = 'STATUS_STOPPED',
/** Stop order has been triggered. This means the trigger conditions have been met, and the stop order was executed. */
STATUS_TRIGGERED = 'STATUS_TRIGGERED',
/** Stop order has been submitted to the network but does not have a status yet */
STATUS_UNSPECIFIED = 'STATUS_UNSPECIFIED'
}
/** Percentage movement in the price at which a stop order will trigger. */
export type StopOrderTrailingPercentOffset = {
__typename?: 'StopOrderTrailingPercentOffset';
trailingPercentOffset: Scalars['String'];
};
export type StopOrderTrigger = StopOrderPrice | StopOrderTrailingPercentOffset;
/** Valid stop order trigger direction. The trigger direction determines whether the price should rise above or fall below the stop order trigger. */
export enum StopOrderTriggerDirection {
/** The price should fall below the trigger. */
TRIGGER_DIRECTION_FALLS_BELOW = 'TRIGGER_DIRECTION_FALLS_BELOW',
/** The price should rise above the trigger. */
TRIGGER_DIRECTION_RISES_ABOVE = 'TRIGGER_DIRECTION_RISES_ABOVE'
}
/** Subscriptions allow a caller to receive new information as it is available from the Vega network. */
export type Subscription = {
__typename?: 'Subscription';
@@ -4314,6 +4586,40 @@ export type SubscriptionvotesArgs = {
proposalId?: InputMaybe<Scalars['ID']>;
};
export type SuccessorConfiguration = {
__typename?: 'SuccessorConfiguration';
/** Decimal value between 0 and 1, specifying the fraction of the insurance pool balance is carried over from the parent market to the successor. */
insurancePoolFraction: Scalars['String'];
/** ID of the market this proposal will succeed */
parentMarketId: Scalars['String'];
};
export type SuccessorMarket = {
__typename?: 'SuccessorMarket';
/** The market */
market: Market;
/** Proposals for child markets */
proposals?: Maybe<Array<Maybe<Proposal>>>;
};
/** Connection type for retrieving cursor-based paginated market information */
export type SuccessorMarketConnection = {
__typename?: 'SuccessorMarketConnection';
/** The markets in this connection */
edges: Array<SuccessorMarketEdge>;
/** The pagination information */
pageInfo: PageInfo;
};
/** Edge type containing the market and cursor information returned by a MarketConnection */
export type SuccessorMarketEdge = {
__typename?: 'SuccessorMarketEdge';
/** The cursor for this market */
cursor: Scalars['String'];
/** The market */
node: SuccessorMarket;
};
/** TargetStakeParameters contains parameters used in target stake calculation */
export type TargetStakeParameters = {
__typename?: 'TargetStakeParameters';
@@ -4546,7 +4852,7 @@ export type TransferEdge = {
node: Transfer;
};
export type TransferKind = OneOffTransfer | RecurringTransfer;
export type TransferKind = OneOffGovernanceTransfer | OneOffTransfer | RecurringGovernanceTransfer | RecurringTransfer;
export type TransferResponse = {
__typename?: 'TransferResponse';
@@ -4593,6 +4899,10 @@ export enum TransferType {
TRANSFER_TYPE_CLEAR_ACCOUNT = 'TRANSFER_TYPE_CLEAR_ACCOUNT',
/** Funds deposited to general account */
TRANSFER_TYPE_DEPOSIT = 'TRANSFER_TYPE_DEPOSIT',
/** An internal instruction to transfer a quantity corresponding to an active spot order from a general account into a party holding account */
TRANSFER_TYPE_HOLDING_LOCK = 'TRANSFER_TYPE_HOLDING_LOCK',
/** An internal instruction to transfer an excess quantity corresponding to an active spot order from a holding account into a party general account */
TRANSFER_TYPE_HOLDING_RELEASE = 'TRANSFER_TYPE_HOLDING_RELEASE',
/** Infrastructure fee received into general account */
TRANSFER_TYPE_INFRASTRUCTURE_FEE_DISTRIBUTE = 'TRANSFER_TYPE_INFRASTRUCTURE_FEE_DISTRIBUTE',
/** Infrastructure fee paid from general account */
@@ -4619,6 +4929,8 @@ export enum TransferType {
TRANSFER_TYPE_MTM_WIN = 'TRANSFER_TYPE_MTM_WIN',
/** Reward payout received */
TRANSFER_TYPE_REWARD_PAYOUT = 'TRANSFER_TYPE_REWARD_PAYOUT',
/** Spot trade delivery */
TRANSFER_TYPE_SPOT = 'TRANSFER_TYPE_SPOT',
/** A network internal instruction for the collateral engine to move funds from the pending transfers pool account into the destination account */
TRANSFER_TYPE_TRANSFER_FUNDS_DISTRIBUTE = 'TRANSFER_TYPE_TRANSFER_FUNDS_DISTRIBUTE',
/** A network internal instruction for the collateral engine to move funds from a user's general account into the pending transfers pool */
+15
View File
@@ -321,6 +321,15 @@ export const ProposalRejectionReasonMapping: {
PROPOSAL_ERROR_UNSUPPORTED_TRADING_MODE: 'Unsupported trading mode',
PROPOSAL_ERROR_ERC20_ADDRESS_ALREADY_IN_USE:
'ERC20 address already in use by an existing asset',
PROPOSAL_ERROR_GOVERNANCE_CANCEL_TRANSFER_PROPOSAL_INVALID:
'PROPOSAL_ERROR_GOVERNANCE_CANCEL_TRANSFER_PROPOSAL_INVALID',
PROPOSAL_ERROR_GOVERNANCE_TRANSFER_PROPOSAL_FAILED:
'PROPOSAL_ERROR_GOVERNANCE_TRANSFER_PROPOSAL_FAILED',
PROPOSAL_ERROR_GOVERNANCE_TRANSFER_PROPOSAL_INVALID:
'PROPOSAL_ERROR_GOVERNANCE_TRANSFER_PROPOSAL_INVALID',
PROPOSAL_ERROR_INVALID_SPOT: 'PROPOSAL_ERROR_INVALID_SPOT',
PROPOSAL_ERROR_INVALID_SUCCESSOR_MARKET:
'PROPOSAL_ERROR_INVALID_SUCCESSOR_MARKET',
};
/**
@@ -419,6 +428,9 @@ export const TransferTypeMapping: TransferTypeMap = {
TRANSFER_TYPE_TRANSFER_FUNDS_DISTRIBUTE: 'Transfer received',
TRANSFER_TYPE_CLEAR_ACCOUNT: 'Market accounts cleared',
TRANSFER_TYPE_CHECKPOINT_BALANCE_RESTORE: 'Balances restored',
TRANSFER_TYPE_HOLDING_LOCK: 'TRANSFER_TYPE_HOLDING_LOCK',
TRANSFER_TYPE_HOLDING_RELEASE: 'TRANSFER_TYPE_HOLDING_RELEASE',
TRANSFER_TYPE_SPOT: 'TRANSFER_TYPE_SPOT',
};
export const DescriptionTransferTypeMapping: TransferTypeMap = {
@@ -446,6 +458,9 @@ export const DescriptionTransferTypeMapping: TransferTypeMap = {
TRANSFER_TYPE_CLEAR_ACCOUNT: `Market-related accounts emptied, and balances moved, because the market has closed`,
TRANSFER_TYPE_UNSPECIFIED: 'Default value, always invalid',
TRANSFER_TYPE_CHECKPOINT_BALANCE_RESTORE: `Balances are being restored to the user's account following a checkpoint restart of the network`,
TRANSFER_TYPE_HOLDING_LOCK: '-',
TRANSFER_TYPE_HOLDING_RELEASE: '-',
TRANSFER_TYPE_SPOT: '-',
};
type DispatchMetricLabel = {
@@ -30,14 +30,14 @@ const primary = [
'enabled:active:bg-vega-yellow-550 enabled:active:border-vega-yellow-550',
];
const secondary = [
'text-white dark:text-black',
'text-white',
'border-vega-pink',
'dark:bg-vega-pink bg-vega-pink-550',
'enabled:hover:bg-vega-pink enabled:hover:border-vega-pink',
'enabled:active:bg-vega-pink enabled:active:border-vega-pink',
];
const ternary = [
'text-white dark:text-black',
'text-black',
'border-vega-green',
'dark:bg-vega-green bg-vega-green-550',
'enabled:hover:bg-vega-green enabled:hover:border-vega-green',
@@ -15,6 +15,13 @@ Default.args = {
label: 'Regular checkbox',
};
export const Overflow = Template.bind({});
Overflow.args = {
name: 'overflow',
label:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
};
export const Disabled = Template.bind({});
Disabled.args = {
disabled: true,
@@ -20,7 +20,7 @@ export const Checkbox = ({
disabled = false,
}: CheckboxProps) => {
const rootClasses = classNames(
'relative flex justify-center items-center w-[15px] h-[15px]',
'relative flex justify-center items-center w-[15px] h-[15px] mt-1',
'border rounded-sm overflow-hidden',
{
'opacity-40 cursor-default': disabled,
@@ -30,7 +30,7 @@ export const Checkbox = ({
);
return (
<div className="flex gap-1 items-center">
<div className="flex gap-1">
<CheckboxPrimitive.Root
name={name}
id={name}
@@ -45,7 +45,7 @@ export function Dialog({
'dark:bg-black bg-white dark:text-white',
getIntentBorder(intent),
{
'w-[620px]': size === 'small',
'w-[520px]': size === 'small',
'w-[720px] lg:w-[940px]': size === 'medium',
}
);
@@ -77,7 +77,7 @@ export function Dialog({
className="absolute p-2 top-0 right-0 md:top-2 md:right-2"
data-testid="dialog-close"
>
<VegaIcon name={VegaIconNames.CROSS} />
<VegaIcon name={VegaIconNames.CROSS} size={24} />
</DialogPrimitives.Close>
)}
<div className="flex gap-4 max-w-full">
@@ -74,11 +74,11 @@ export const DropdownMenuContent = forwardRef<
React.ComponentProps<typeof DropdownMenuPrimitive.Content>
>(({ className, ...contentProps }, forwardedRef) => (
<DropdownMenuPrimitive.Content
{...contentProps}
ref={forwardedRef}
sideOffset={10}
className="min-w-[290px] bg-vega-light-100 dark:bg-vega-dark-100 p-2 rounded z-20 text-black dark:text-white border border-vega-light-200 dark:border-vega-dark-200"
align="start"
sideOffset={10}
{...contentProps}
/>
));
@@ -0,0 +1,7 @@
export const IconChevronLeft = ({ size = 16 }: { size: number }) => {
return (
<svg width={size} height={size} viewBox="0 0 16 16">
<path d="M10.38 1.62L11.13 2.38L5.5 8L11.13 13.62L10.38 14.38L4 8L10.38 1.62Z" />
</svg>
);
};
@@ -2,6 +2,7 @@ import { IconArrowDown } from './svg-icons/icon-arrow-down';
import { IconArrowRight } from './svg-icons/icon-arrow-right';
import { IconBreakdown } from './svg-icons/icon-breakdown';
import { IconChevronDown } from './svg-icons/icon-chevron-down';
import { IconChevronLeft } from './svg-icons/icon-chevron-left';
import { IconChevronUp } from './svg-icons/icon-chevron-up';
import { IconCopy } from './svg-icons/icon-copy';
import { IconCross } from './svg-icons/icon-cross';
@@ -26,6 +27,7 @@ export enum VegaIconNames {
ARROW_RIGHT = 'arrow-right',
BREAKDOWN = 'breakdown',
CHEVRON_DOWN = 'chevron-down',
CHEVRON_LEFT = 'chevron-left',
CHEVRON_UP = 'chevron-up',
COPY = 'copy',
CROSS = 'cross',
@@ -53,6 +55,7 @@ export const VegaIconNameMap: Record<
'arrow-down': IconArrowDown,
'arrow-right': IconArrowRight,
'chevron-down': IconChevronDown,
'chevron-left': IconChevronLeft,
'chevron-up': IconChevronUp,
'open-external': IconOpenExternal,
'question-mark': IconQuestionMark,
+3 -2
View File
@@ -8,10 +8,11 @@ export const defaultFormElement = (hasError?: boolean) =>
'flex items-center w-full text-sm',
'p-2 border-2 rounded',
'bg-transparent',
'border border-vega-light-200 dark:border-vega-dark-200',
'border',
'focus:border-vega-light-300 dark:focus:border-vega-dark-300',
'disabled:opacity-60',
{
'border-vega-pink': hasError,
'border-vega-pink text-vega-pink': hasError,
'border-vega-light-200 dark:border-vega-dark-200': !hasError,
}
);
@@ -1,7 +1,10 @@
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import { Link } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { ReactNode } from 'react';
import type { VegaConnector } from '../connectors';
import { RestConnector } from '../connectors';
export const ConnectDialogTitle = ({ children }: { children: ReactNode }) => {
return (
@@ -18,11 +21,35 @@ export const ConnectDialogContent = ({ children }: { children: ReactNode }) => {
return <div>{children}</div>;
};
export const ConnectDialogFooter = ({ children }: { children?: ReactNode }) => {
export const ConnectDialogFooter = ({
connector,
}: {
connector: VegaConnector | undefined;
}) => {
const wrapperClasses = classNames(
'flex justify-center gap-4',
'px-4 md:px-8 pt-4 md:pt-6',
'border-t border-vega-light-200 dark:border-vega-dark-200',
'text-vega-light-400 dark:text-vega-dark-400'
);
const isHostedWalletSelected = connector instanceof RestConnector;
return (
<footer className="flex justify-center gap-4 px-4 md:px-8 pt-4 md:pt-6 -mx-4 md:-mx-8 border-t border-neutral-500 text-neutral-500 dark:text-neutral-400 mt-6">
{children ? (
children
<footer className={wrapperClasses}>
{isHostedWalletSelected ? (
<p className="text-center">
{t('For demo purposes get a ')}
<Link
href={ExternalLinks.VEGA_WALLET_HOSTED_URL}
target="_blank"
rel="noreferrer"
>
{t('hosted wallet')}
</Link>
{t(', or for the real experience create a wallet in the ')}
<Link href={ExternalLinks.VEGA_WALLET_URL}>
{t('Vega wallet app')}
</Link>
</p>
) : (
<>
<Link href={ExternalLinks.VEGA_WALLET_URL}>
@@ -16,6 +16,7 @@ import {
import type { VegaConnectDialogProps } from '..';
import {
ClientErrors,
InjectedConnector,
JsonRpcConnector,
RestConnector,
ViewConnector,
@@ -24,6 +25,12 @@ import {
import { useEnvironment } from '@vegaprotocol/environment';
import type { ChainIdQuery } from './__generated__/ChainId';
import { ChainIdDocument } from './__generated__/ChainId';
import {
mockBrowserWallet,
clearBrowserWallet,
delayedReject,
delayedResolve,
} from '../test-helpers';
const mockUpdateDialogOpen = jest.fn();
const mockCloseVegaDialog = jest.fn();
@@ -49,10 +56,12 @@ const INITIAL_KEY = 'some-key';
const rest = new RestConnector();
const jsonRpc = new JsonRpcConnector();
const view = new ViewConnector(INITIAL_KEY);
const injected = new InjectedConnector();
const connectors = {
rest,
jsonRpc,
view,
injected,
};
beforeEach(() => {
jest.clearAllMocks();
@@ -105,7 +114,7 @@ describe('VegaConnectDialog', () => {
expect(screen.getByTestId('connector-jsonRpc')).toHaveTextContent(
'Connect Vega wallet'
);
expect(screen.getByTestId('connector-hosted')).toHaveTextContent(
expect(screen.getByTestId('connector-rest')).toHaveTextContent(
'Hosted Fairground wallet'
);
expect(screen.getByTestId('connector-view')).toHaveTextContent(
@@ -113,6 +122,17 @@ describe('VegaConnectDialog', () => {
);
});
it('displays browser wallet option if detected on window object', async () => {
mockBrowserWallet();
render(generateJSX());
const list = await screen.findByTestId('connectors-list');
expect(list.children).toHaveLength(4);
expect(screen.getByTestId('connector-injected')).toHaveTextContent(
'Connect Web wallet'
);
clearBrowserWallet();
});
describe('RestConnector', () => {
it('connects', async () => {
const spy = jest
@@ -229,17 +249,19 @@ describe('VegaConnectDialog', () => {
beforeEach(() => {
spyOnCheckCompat = jest
.spyOn(connectors.jsonRpc, 'checkCompat')
.mockImplementation(() => delayedResolve(true));
.mockImplementation(() => delayedResolve(true, delay));
spyOnGetChainId = jest
.spyOn(connectors.jsonRpc, 'getChainId')
.mockImplementation(() => delayedResolve({ chainID: mockChainId }));
.mockImplementation(() =>
delayedResolve({ chainID: mockChainId }, delay)
);
spyOnConnectWallet = jest
.spyOn(connectors.jsonRpc, 'connectWallet')
.mockImplementation(() => delayedResolve(null));
.mockImplementation(() => delayedResolve(null, delay));
spyOnConnect = jest
.spyOn(connectors.jsonRpc, 'connect')
.mockImplementation(() =>
delayedResolve([{ publicKey: 'pubkey', name: 'test key 1' }])
delayedResolve([{ publicKey: 'pubkey', name: 'test key 1' }], delay)
);
});
@@ -351,18 +373,6 @@ describe('VegaConnectDialog', () => {
expect(screen.getByText('An unknown error occurred')).toBeInTheDocument();
});
function delayedResolve<T>(result: T): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => resolve(result), delay);
});
}
function delayedReject<T>(result: T): Promise<T> {
return new Promise((_, reject) => {
setTimeout(() => reject(result), delay);
});
}
async function selectJsonRpc() {
expect(await screen.findByRole('dialog')).toBeInTheDocument();
fireEvent.click(await screen.findByTestId('connector-jsonRpc'));
@@ -439,4 +449,109 @@ describe('VegaConnectDialog', () => {
});
});
});
describe('InjectedConnector', () => {
beforeAll(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
localStorage.clear();
});
afterEach(() => {
clearBrowserWallet();
});
it('connects', async () => {
const delay = 100;
const vegaWindow = {
getChainId: jest.fn(() =>
delayedResolve({ chainID: mockChainId }, delay)
),
connectWallet: jest.fn(() => delayedResolve(null, delay)),
disconnectWallet: jest.fn(() => delayedResolve(undefined, delay)),
listKeys: jest.fn(() =>
delayedResolve(
{
keys: [{ name: 'test key', publicKey: '0x123' }],
},
100
)
),
};
mockBrowserWallet(vegaWindow);
render(generateJSX());
await selectInjected();
// Chain check
expect(screen.getByText('Verifying chain')).toBeInTheDocument();
expect(vegaWindow.getChainId).toHaveBeenCalled();
await act(async () => {
jest.advanceTimersByTime(delay);
});
// Await user connect
expect(screen.getByText('Connecting...')).toBeInTheDocument();
expect(vegaWindow.connectWallet).toHaveBeenCalled();
await act(async () => {
jest.advanceTimersByTime(delay);
});
// Connect (list keys)
expect(vegaWindow.listKeys).toHaveBeenCalled();
await act(async () => {
jest.advanceTimersByTime(delay);
});
expect(screen.getByText('Successfully connected')).toBeInTheDocument();
await act(async () => {
jest.advanceTimersByTime(CLOSE_DELAY);
});
expect(mockCloseVegaDialog).toHaveBeenCalledWith();
});
it('handles invalid chain', async () => {
const delay = 100;
const invalidChain = 'invalid chain';
const vegaWindow = {
getChainId: jest.fn(() =>
delayedResolve({ chainID: invalidChain }, delay)
),
connectWallet: jest.fn(() => delayedResolve(null, delay)),
disconnectWallet: jest.fn(() => delayedResolve(undefined, delay)),
listKeys: jest.fn(() =>
delayedResolve(
{
keys: [{ name: 'test key', publicKey: '0x123' }],
},
100
)
),
};
mockBrowserWallet(vegaWindow);
render(generateJSX());
await selectInjected();
// Chain check
expect(screen.getByText('Verifying chain')).toBeInTheDocument();
expect(vegaWindow.getChainId).toHaveBeenCalled();
await act(async () => {
jest.advanceTimersByTime(delay);
});
expect(screen.getByText('Wrong network')).toBeInTheDocument();
expect(
screen.getByText(
new RegExp(`set your wallet network in your app to "${mockChainId}"`)
)
).toBeInTheDocument();
});
async function selectInjected() {
expect(await screen.findByRole('dialog')).toBeInTheDocument();
fireEvent.click(await screen.findByTestId('connector-injected'));
}
});
});
+150 -187
View File
@@ -3,45 +3,50 @@ import {
Button,
Dialog,
FormGroup,
Icon,
Input,
Link,
Loader,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useCallback, useState } from 'react';
import type { WalletClientError } from '@vegaprotocol/wallet-client';
import { t } from '@vegaprotocol/i18n';
import type { VegaConnector } from '../connectors';
import { InjectedConnector } from '../connectors';
import { ViewConnector } from '../connectors';
import { JsonRpcConnector, RestConnector } from '../connectors';
import { RestConnectorForm } from './rest-connector-form';
import { JsonRpcConnectorForm } from './json-rpc-connector-form';
import {
Networks,
useEnvironment,
ExternalLinks,
} from '@vegaprotocol/environment';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import {
ConnectDialogContent,
ConnectDialogFooter,
ConnectDialogTitle,
} from './connect-dialog-elements';
import type { Status } from '../use-json-rpc-connect';
import type { Status as JsonRpcStatus } from '../use-json-rpc-connect';
import type { Status as InjectedStatus } from '../use-injected-connector';
import { useJsonRpcConnect } from '../use-json-rpc-connect';
import { ViewConnectorForm } from './view-connector-form';
import { useChainIdQuery } from './__generated__/ChainId';
import { useVegaWallet } from '../use-vega-wallet';
import { useInjectedConnector } from '../use-injected-connector';
import { InjectedConnectorForm } from './injected-connector-form';
export const CLOSE_DELAY = 1700;
type Connectors = { [key: string]: VegaConnector };
type WalletType = 'jsonRpc' | 'hosted' | 'view';
export type WalletType = 'injected' | 'jsonRpc' | 'rest' | 'view';
export interface VegaConnectDialogProps {
connectors: Connectors;
onChangeOpen?: (open: boolean) => void;
riskMessage?: React.ReactNode;
}
export interface VegaWalletDialogStore {
vegaWalletDialogOpen: boolean;
updateVegaWalletDialog: (open: boolean) => void;
openVegaWalletDialog: () => void;
closeVegaWalletDialog: () => void;
}
export const useVegaWalletDialogStore = create<VegaWalletDialogStore>()(
(set) => ({
vegaWalletDialogOpen: false,
@@ -52,32 +57,20 @@ export const useVegaWalletDialogStore = create<VegaWalletDialogStore>()(
})
);
export interface VegaWalletDialogStore {
vegaWalletDialogOpen: boolean;
updateVegaWalletDialog: (open: boolean) => void;
openVegaWalletDialog: () => void;
closeVegaWalletDialog: () => void;
}
export const VegaConnectDialog = ({
connectors,
onChangeOpen,
riskMessage,
}: VegaConnectDialogProps) => {
const { disconnect, acknowledgeNeeded } = useVegaWallet();
const vegaWalletDialogOpen = useVegaWalletDialogStore(
(store) => store.vegaWalletDialogOpen
);
const updateVegaWalletDialog = useVegaWalletDialogStore(
(store) => (open: boolean) => {
store.updateVegaWalletDialog(open);
onChangeOpen?.(open);
}
);
const closeVegaWalletDialog = useVegaWalletDialogStore((store) => () => {
store.closeVegaWalletDialog();
onChangeOpen?.(false);
});
const { disconnect, acknowledgeNeeded } = useVegaWallet();
const onVegaWalletDialogChange = useCallback(
(open: boolean) => {
updateVegaWalletDialog(open);
@@ -88,41 +81,9 @@ export const VegaConnectDialog = ({
[updateVegaWalletDialog, acknowledgeNeeded, disconnect]
);
const { data, error, loading } = useChainIdQuery();
const renderContent = () => {
if (error) {
return (
<ConnectDialogContent>
<ConnectDialogTitle>
{t('Could not retrieve chain id')}
</ConnectDialogTitle>
<ConnectDialogFooter />
</ConnectDialogContent>
);
}
if (loading || !data) {
return (
<ConnectDialogContent>
<ConnectDialogTitle>{t('Fetching chain ID')}</ConnectDialogTitle>
<div className="flex justify-center items-center my-6">
<Loader />
</div>
<ConnectDialogFooter />
</ConnectDialogContent>
);
}
return (
<ConnectDialogContainer
connectors={connectors}
closeDialog={closeVegaWalletDialog}
appChainId={data.statistics.chainId}
riskMessage={riskMessage}
/>
);
};
// Ensure we have a chain Id so we can compare with wallet chain id.
// This value will already be in the cache, if it failed the app wont render
const { data } = useChainIdQuery();
return (
<Dialog
@@ -130,29 +91,35 @@ export const VegaConnectDialog = ({
size="small"
onChange={onVegaWalletDialogChange}
>
{renderContent()}
{data && (
<ConnectDialogContainer
connectors={connectors}
appChainId={data.statistics.chainId}
riskMessage={riskMessage}
/>
)}
</Dialog>
);
};
const ConnectDialogContainer = ({
connectors,
closeDialog,
appChainId,
riskMessage,
}: {
connectors: Connectors;
closeDialog: () => void;
appChainId: string;
riskMessage?: React.ReactNode;
}) => {
const { VEGA_WALLET_URL, VEGA_ENV, HOSTED_WALLET_URL } = useEnvironment();
const closeDialog = useVegaWalletDialogStore(
(store) => store.closeVegaWalletDialog
);
const [selectedConnector, setSelectedConnector] = useState<VegaConnector>();
const [walletUrl, setWalletUrl] = useState(VEGA_WALLET_URL || '');
const [walletType, setWalletType] = useState<WalletType>();
const reset = useCallback(() => {
setSelectedConnector(undefined);
setWalletType(undefined);
}, []);
const delayedOnConnect = useCallback(() => {
@@ -161,52 +128,59 @@ const ConnectDialogContainer = ({
}, CLOSE_DELAY);
}, [closeDialog]);
const { connect, ...jsonRpcState } = useJsonRpcConnect(delayedOnConnect);
const { connect: jsonRpcConnect, ...jsonRpcState } =
useJsonRpcConnect(delayedOnConnect);
const { connect: injectedConnect, ...injectedState } =
useInjectedConnector(delayedOnConnect);
const handleSelect = (type: WalletType, isHosted = false) => {
let connector;
const handleSelect = (type: WalletType) => {
const connector = connectors[type];
if (isHosted) {
// If the user has selected hosted wallet ensure that we are connecting to https://vega-hosted-wallet.on.fleek.co/
// otherwise use the default walletUrl or what has been put in the input
connector = connectors['rest'];
connector.url = HOSTED_WALLET_URL || walletUrl;
} else {
connector = connectors[type];
connector.url = walletUrl;
}
// If type is rest user has selected the hosted wallet option. So here
// we ensure that we are connecting to https://vega-hosted-wallet.on.fleek.co/
// otherwise use walletUrl which defaults to the localhost:1789
connector.url = type === 'rest' ? HOSTED_WALLET_URL : walletUrl;
if (!connector) {
// we should never get here unless connectors are not configured correctly
throw new Error(`Connector type: ${type} not configured`);
}
setSelectedConnector(connector);
setWalletType(type);
// Immediately connect on selection if jsonRpc is selected, we can't do this
// for rest because we need to show an authentication form
if (connector instanceof JsonRpcConnector) {
connect(connector, appChainId);
jsonRpcConnect(connector, appChainId);
} else if (connector instanceof InjectedConnector) {
injectedConnect(connector, appChainId);
}
};
return selectedConnector !== undefined && walletType !== undefined ? (
<SelectedForm
type={walletType}
connector={selectedConnector}
jsonRpcState={jsonRpcState}
onConnect={closeDialog}
appChainId={appChainId}
reset={reset}
riskMessage={riskMessage}
/>
) : (
<ConnectorList
walletUrl={walletUrl}
setWalletUrl={setWalletUrl}
onSelect={handleSelect}
isMainnet={VEGA_ENV === Networks.MAINNET}
/>
return (
<>
<ConnectDialogContent>
{selectedConnector !== undefined ? (
<SelectedForm
connector={selectedConnector}
jsonRpcState={jsonRpcState}
injectedState={injectedState}
onConnect={closeDialog}
appChainId={appChainId}
reset={reset}
riskMessage={riskMessage}
/>
) : (
<ConnectorList
walletUrl={walletUrl}
setWalletUrl={setWalletUrl}
onSelect={handleSelect}
isMainnet={VEGA_ENV === Networks.MAINNET}
/>
)}
</ConnectDialogContent>
<ConnectDialogFooter connector={selectedConnector} />
</>
);
};
@@ -216,136 +190,129 @@ const ConnectorList = ({
setWalletUrl,
isMainnet,
}: {
onSelect: (type: WalletType, isHosted?: boolean) => void;
onSelect: (type: WalletType) => void;
walletUrl: string;
setWalletUrl: (value: string) => void;
isMainnet: boolean;
}) => {
return (
<>
<ConnectDialogContent>
<ConnectDialogTitle>{t('Connect')}</ConnectDialogTitle>
<CustomUrlInput walletUrl={walletUrl} setWalletUrl={setWalletUrl} />
<ul data-testid="connectors-list" className="mb-6">
<ConnectDialogTitle>{t('Connect')}</ConnectDialogTitle>
<CustomUrlInput walletUrl={walletUrl} setWalletUrl={setWalletUrl} />
<ul data-testid="connectors-list" className="mb-6">
<li className="mb-4 last:mb-0">
<ConnectionOption
type="jsonRpc"
text={t('Connect Vega wallet')}
onClick={() => onSelect('jsonRpc')}
/>
</li>
{'vega' in window && (
<li className="mb-4 last:mb-0">
<ConnectionOption
type="jsonRpc"
text={t('Connect Vega wallet')}
onClick={() => onSelect('jsonRpc')}
type="injected"
text={t('Connect Web wallet')}
onClick={() => onSelect('injected')}
/>
</li>
{!isMainnet && (
<li className="mb-4 last:mb-0">
<ConnectionOption
type="hosted"
text={t('Hosted Fairground wallet')}
onClick={() => onSelect('hosted', true)}
/>
</li>
)}
)}
{!isMainnet && (
<li className="mb-4 last:mb-0">
<div className="my-4 text-center text-vega-dark-400">{t('OR')}</div>
<ConnectionOption
type="view"
text={t('View as vega user')}
onClick={() => onSelect('view')}
type="rest"
text={t('Hosted Fairground wallet')}
onClick={() => onSelect('rest')}
/>
</li>
</ul>
</ConnectDialogContent>
<ConnectDialogFooter />
)}
<li className="mb-4 last:mb-0">
<div className="my-4 text-center">{t('OR')}</div>
<ConnectionOption
type="view"
text={t('View as vega user')}
onClick={() => onSelect('view')}
/>
</li>
</ul>
</>
);
};
const SelectedForm = ({
type,
connector,
appChainId,
jsonRpcState,
injectedState,
reset,
onConnect,
riskMessage,
}: {
type: WalletType;
connector: VegaConnector;
appChainId: string;
jsonRpcState: {
status: Status;
status: JsonRpcStatus;
error: WalletClientError | null;
};
injectedState: {
status: InjectedStatus;
error: Error | null;
};
reset: () => void;
onConnect: () => void;
riskMessage?: React.ReactNode;
}) => {
if (connector instanceof InjectedConnector) {
return (
<InjectedConnectorForm
status={injectedState.status}
error={injectedState.error}
onConnect={onConnect}
appChainId={appChainId}
reset={reset}
riskMessage={riskMessage}
/>
);
}
if (connector instanceof RestConnector) {
return (
<>
<ConnectDialogContent>
<button
onClick={reset}
className="absolute p-2 top-0 left-0 md:top-2 md:left-2"
data-testid="back-button"
>
<Icon name={'chevron-left'} ariaLabel="back" size={4} />
</button>
<ConnectDialogTitle>{t('Connect')}</ConnectDialogTitle>
<div className="mb-2">
<RestConnectorForm connector={connector} onConnect={onConnect} />
</div>
</ConnectDialogContent>
{type === 'hosted' ? (
<ConnectDialogFooter>
<p className="text-center">
{t('For demo purposes get a ')}
<Link
href={ExternalLinks.VEGA_WALLET_HOSTED_URL}
target="_blank"
rel="noreferrer"
>
{t('hosted wallet')}
</Link>
{t(', or for the real experience create a wallet in the ')}
<Link href={ExternalLinks.VEGA_WALLET_URL}>
{t('Vega wallet app')}
</Link>
</p>
</ConnectDialogFooter>
) : (
<ConnectDialogFooter />
)}
<button
onClick={reset}
className="absolute p-2 top-0 left-0 md:top-2 md:left-2"
data-testid="back-button"
>
<VegaIcon name={VegaIconNames.CHEVRON_LEFT} />
</button>
<ConnectDialogTitle>{t('Connect')}</ConnectDialogTitle>
<div className="mb-2">
<RestConnectorForm connector={connector} onConnect={onConnect} />
</div>
</>
);
}
if (connector instanceof JsonRpcConnector) {
return (
<ConnectDialogContent>
<JsonRpcConnectorForm
connector={connector}
status={jsonRpcState.status}
error={jsonRpcState.error}
onConnect={onConnect}
appChainId={appChainId}
reset={reset}
riskMessage={riskMessage}
/>
</ConnectDialogContent>
<JsonRpcConnectorForm
connector={connector}
status={jsonRpcState.status}
error={jsonRpcState.error}
onConnect={onConnect}
appChainId={appChainId}
reset={reset}
riskMessage={riskMessage}
/>
);
}
if (connector instanceof ViewConnector) {
return (
<>
<ConnectDialogContent>
<ViewConnectorForm
connector={connector}
onConnect={onConnect}
reset={reset}
/>
</ConnectDialogContent>
<ConnectDialogFooter />
</>
<ViewConnectorForm
connector={connector}
onConnect={onConnect}
reset={reset}
/>
);
}
@@ -366,12 +333,12 @@ const ConnectionOption = ({
onClick={onClick}
size="lg"
fill={true}
variant={['hosted', 'view'].includes(type) ? 'default' : 'primary'}
variant={['rest', 'view'].includes(type) ? 'default' : 'primary'}
data-testid={`connector-${type}`}
>
<span className="-mx-6 flex text-left justify-between items-center">
<span className="-mx-10 flex text-left justify-between items-center">
{text}
<Icon name="chevron-right" />
<VegaIcon name={VegaIconNames.ARROW_RIGHT} />
</span>
</Button>
);
@@ -387,9 +354,7 @@ const CustomUrlInput = ({
const [urlInputExpanded, setUrlInputExpanded] = useState(false);
return urlInputExpanded ? (
<>
<p className="mb-2 text-neutral-600 dark:text-neutral-400">
{t('Custom wallet location')}
</p>
<p className="mb-2">{t('Custom wallet location')}</p>
<FormGroup
labelFor="wallet-url"
label={t('Custom wallet location')}
@@ -401,12 +366,10 @@ const CustomUrlInput = ({
name="wallet-url"
/>
</FormGroup>
<p className="mb-2 text-neutral-600 dark:text-neutral-400">
{t('Choose wallet app to connect')}
</p>
<p className="mb-2">{t('Choose wallet app to connect')}</p>
</>
) : (
<p className="mb-6 text-neutral-600 dark:text-neutral-400">
<p className="mb-6">
{t(
'Choose wallet app to connect, or to change port or server URL enter a '
)}
@@ -0,0 +1,153 @@
import { t } from '@vegaprotocol/i18n';
import { Status } from '../use-injected-connector';
import { ConnectDialogTitle } from './connect-dialog-elements';
import type { ReactNode } from 'react';
import {
Button,
ButtonLink,
Diamond,
Loader,
Tick,
} from '@vegaprotocol/ui-toolkit';
import { setAcknowledged } from '../storage';
import { useVegaWallet } from '../use-vega-wallet';
export const InjectedConnectorForm = ({
status,
onConnect,
riskMessage,
appChainId,
reset,
error,
}: {
// connector: JsonRpcConnector;
appChainId: string;
status: Status;
error: Error | null;
onConnect: () => void;
reset: () => void;
riskMessage?: React.ReactNode;
}) => {
const { disconnect } = useVegaWallet();
if (status === Status.Idle) {
return null;
}
if (status === Status.Error) {
return <Error error={error} appChainId={appChainId} onTryAgain={reset} />;
}
if (status === Status.GettingChainId) {
return (
<>
<ConnectDialogTitle>{t('Verifying chain')}</ConnectDialogTitle>
<Center>
<Loader />
</Center>
</>
);
}
if (status === Status.Connected) {
return (
<>
<ConnectDialogTitle>{t('Successfully connected')}</ConnectDialogTitle>
<Center>
<Tick />
</Center>
</>
);
}
if (status === Status.Connecting) {
return (
<>
<ConnectDialogTitle>{t('Connecting...')}</ConnectDialogTitle>
<Center>
<Diamond />
</Center>
<p className="text-center">
{t(
"Approve the connection from your Vega wallet app. If you have multiple wallets you'll need to choose which to connect with."
)}
</p>
</>
);
}
if (status === Status.AcknowledgeNeeded) {
const setConnection = () => {
setAcknowledged();
onConnect();
};
const handleDisagree = () => {
disconnect();
onConnect(); // this is dialog closing
};
return (
<>
<ConnectDialogTitle>{t('Understand the risk')}</ConnectDialogTitle>
{riskMessage}
<div className="grid grid-cols-2 gap-5">
<div>
<Button onClick={handleDisagree} fill>
{t('Cancel')}
</Button>
</div>
<div>
<Button onClick={setConnection} variant="primary" fill>
{t('I agree')}
</Button>
</div>
</div>
</>
);
}
return null;
};
const Center = ({ children }: { children: ReactNode }) => {
return (
<div className="flex justify-center items-center my-6">{children}</div>
);
};
const Error = ({
error,
appChainId,
onTryAgain,
}: {
error: Error | null;
appChainId: string;
onTryAgain: () => void;
}) => {
let title = t('Something went wrong');
let text: ReactNode | undefined = t('An unknown error occurred');
const tryAgain: ReactNode | null = (
<p className="text-center">
<ButtonLink onClick={onTryAgain}>{t('Try again')}</ButtonLink>
</p>
);
if (error) {
if (error.message === 'Invalid chain') {
title = t('Wrong network');
text = t(
'To complete your wallet connection, set your wallet network in your app to "%s".',
appChainId
);
} else if (error.message === 'window.vega not found') {
title = t('No wallet detected');
text = t('Vega browser extension not installed');
}
}
return (
<>
<ConnectDialogTitle>{title}</ConnectDialogTitle>
<p className="text-center mb-2 first-letter:uppercase">{text}</p>
{tryAgain}
</>
);
};
@@ -1,21 +1,83 @@
import type { VegaConnector } from './vega-connector';
import { clearConfig, setConfig } from '../storage';
import type { Transaction, VegaConnector } from './vega-connector';
declare global {
interface Vega {
getChainId: () => Promise<{ chainID: string }>;
connectWallet: () => Promise<null>;
disconnectWallet: () => Promise<void>;
listKeys: () => Promise<{
keys: Array<{ name: string; publicKey: string }>;
}>;
sendTransaction: (params: {
publicKey: string;
transaction: Transaction;
sendingMode: 'TYPE_SYNC';
}) => Promise<{
receivedAt: string;
sentAt: string;
transaction: {
from: {
pubKey: string;
};
inputData: string;
pow: {
tid: string;
nonce: string;
};
signature: {
algo: string;
value: string;
version: number;
};
version: number;
};
transactionHash: string;
}>;
}
interface Window {
vega: Vega;
}
}
/**
* Dummy injected connector that we may use when browser wallet is implemented
*/
export class InjectedConnector implements VegaConnector {
description = 'Connects using the Vega wallet browser extension';
async getChainId() {
return window.vega.getChainId();
}
connectWallet() {
return window.vega.connectWallet();
}
async connect() {
return [{ publicKey: '0x123', name: 'text key' }];
const res = await window.vega.listKeys();
setConfig({
connector: 'injected',
token: null, // no token required for injected
url: null, // no url for injected
});
return res.keys;
}
async disconnect() {
return;
disconnect() {
clearConfig();
return window.vega.disconnectWallet();
}
// @ts-ignore injected connector is not implemented
sendTx() {
throw new Error('Not implemented');
async sendTx(pubKey: string, transaction: Transaction) {
const result = await window.vega.sendTransaction({
publicKey: pubKey,
transaction,
sendingMode: 'TYPE_SYNC' as const,
});
return {
transactionHash: result.transactionHash,
receivedAt: result.receivedAt,
sentAt: result.sentAt,
signature: result.transaction.signature.value,
};
}
}
+1 -1
View File
@@ -411,7 +411,7 @@ export interface PubKey {
}
export interface VegaConnector {
url: string | null;
url?: string | null;
/** Connect to wallet and return keys */
connect(): Promise<PubKey[] | null>;
-1
View File
@@ -106,7 +106,6 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
if (!connector.current) {
throw new Error('No connector');
}
return connector.current.sendTx(pubkey, transaction);
}, []);
+1 -1
View File
@@ -2,7 +2,7 @@ import { LocalStorage } from '@vegaprotocol/utils';
interface ConnectorConfig {
token: string | null;
connector: 'rest' | 'jsonRpc' | 'view';
connector: 'injected' | 'rest' | 'jsonRpc' | 'view';
url: string | null;
}
+39
View File
@@ -0,0 +1,39 @@
export function mockBrowserWallet(overrides?: Partial<Vega>) {
const vega: Vega = {
getChainId: jest.fn().mockReturnValue(Promise.resolve({ chainID: '1' })),
connectWallet: jest.fn().mockReturnValue(Promise.resolve(null)),
disconnectWallet: jest.fn().mockReturnValue(Promise.resolve()),
listKeys: jest
.fn()
.mockReturnValue({ keys: [{ name: 'test key', publicKey: '0x123' }] }),
sendTransaction: jest.fn().mockReturnValue({
code: 1,
data: '',
height: '1',
log: '',
success: true,
txHash: '0x123',
}),
...overrides,
};
// @ts-ignore globalThis has no index signature
globalThis.vega = vega;
return vega;
}
export function clearBrowserWallet() {
// @ts-ignore no index signature on globalThis
delete globalThis['vega'];
}
export function delayedResolve<T>(result: T, delay = 0): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => resolve(result), delay);
});
}
export function delayedReject<T>(result: T, delay = 0): Promise<T> {
return new Promise((_, reject) => {
setTimeout(() => reject(result), delay);
});
}
@@ -0,0 +1,104 @@
import { act, renderHook, waitFor } from '@testing-library/react';
import { Status, useInjectedConnector } from './use-injected-connector';
import type { ReactNode } from 'react';
import { VegaWalletProvider } from './provider';
import { InjectedConnector } from './connectors';
import { mockBrowserWallet } from './test-helpers';
import { useEnvironment } from '@vegaprotocol/environment';
import { Networks } from '@vegaprotocol/environment';
jest.mock('@vegaprotocol/environment');
const setup = (callback = jest.fn()) => {
const wrapper = ({ children }: { children: ReactNode }) => (
<VegaWalletProvider>{children}</VegaWalletProvider>
);
return renderHook(() => useInjectedConnector(callback), { wrapper });
};
const injected = new InjectedConnector();
describe('useInjectedConnector', () => {
beforeEach(() => {
// @ts-ignore useEnvironment has been mocked
useEnvironment.mockImplementation(() => ({ VEGA_ENV: Networks.TESTNET }));
});
it('attempts connection', async () => {
const { result } = setup();
expect(typeof result.current.connect).toBe('function');
expect(result.current.status).toBe(Status.Idle);
expect(result.current.error).toBe(null);
});
it('errors if vega not injected', async () => {
const { result } = setup();
await act(async () => {
result.current.connect(injected, '1');
});
expect(result.current.error?.message).toBe('window.vega not found');
expect(result.current.status).toBe(Status.Error);
});
it('errors if chain ids dont match', async () => {
mockBrowserWallet();
const { result } = setup();
await act(async () => {
result.current.connect(injected, '2'); // default mock chainId is '1'
});
expect(result.current.error?.message).toBe('Invalid chain');
expect(result.current.status).toBe(Status.Error);
});
it('errors if connection throws', async () => {
const callback = jest.fn();
mockBrowserWallet({
getChainId: () => Promise.reject('failed'),
});
const { result } = setup(callback);
await act(async () => {
result.current.connect(injected, '1'); // default mock chainId is '1'
});
expect(result.current.status).toBe(Status.Error);
expect(result.current.error?.message).toBe('injected connection failed');
});
it('connects', async () => {
const callback = jest.fn();
const vega = mockBrowserWallet();
const { result } = setup(callback);
act(() => {
result.current.connect(injected, '1'); // default mock chainId is '1'
});
expect(result.current.status).toBe(Status.GettingChainId);
await waitFor(() => {
expect(vega.connectWallet).toHaveBeenCalled();
expect(vega.listKeys).toHaveBeenCalled();
});
expect(result.current.status).toBe(Status.Connected);
expect(callback).toHaveBeenCalled();
});
it('connects when aknowledgement required', async () => {
const callback = jest.fn();
// @ts-ignore useEnvironment has been mocked
useEnvironment.mockImplementation(() => ({ VEGA_ENV: Networks.MAINNET }));
const vega = mockBrowserWallet();
const { result } = setup(callback);
act(() => {
result.current.connect(injected, '1'); // default mock chainId is '1'
});
await waitFor(() => {
expect(vega.listKeys).toHaveBeenCalled();
});
expect(result.current.status).toBe(Status.AcknowledgeNeeded);
expect(callback).not.toHaveBeenCalled();
});
});
+61
View File
@@ -0,0 +1,61 @@
import { useCallback, useState } from 'react';
import type { InjectedConnector } from './connectors';
import { useVegaWallet } from './use-vega-wallet';
export enum Status {
Idle = 'Idle',
GettingChainId = 'GettingChainId',
Connecting = 'Connecting',
Connected = 'Connected',
Error = 'Error',
AcknowledgeNeeded = 'AcknowledgeNeeded',
}
export const useInjectedConnector = (onConnect: () => void) => {
const { connect, acknowledgeNeeded } = useVegaWallet();
const [status, setStatus] = useState(Status.Idle);
const [error, setError] = useState<Error | null>(null);
const attemptConnect = useCallback(
async (connector: InjectedConnector, appChainId: string) => {
try {
if (!('vega' in window)) {
throw new Error('window.vega not found');
}
setStatus(Status.GettingChainId);
const { chainID } = await connector.getChainId();
if (chainID !== appChainId) {
throw new Error('Invalid chain');
}
setStatus(Status.Connecting);
await connector.connectWallet(); // authorize wallet
await connect(connector); // connect with keys
if (acknowledgeNeeded) {
setStatus(Status.AcknowledgeNeeded);
} else {
setStatus(Status.Connected);
onConnect();
}
} catch (err) {
if (err instanceof Error) {
setError(err);
} else {
setError(new Error('injected connection failed'));
}
setStatus(Status.Error);
}
},
[acknowledgeNeeded, connect, onConnect]
);
return {
status,
error,
connect: attemptConnect,
};
};
-1
View File
@@ -11,7 +11,6 @@ export enum Status {
GettingChainId = 'GettingChainId',
Connecting = 'Connecting',
GettingPerms = 'GettingPerms',
ListingKeys = 'ListingKeys',
Connected = 'Connected',
Error = 'Error',
AcknowledgeNeeded = 'AcknowledgeNeeded',