Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fe0ee3b55 | ||
|
|
ab7c9c433d | ||
|
|
090ac59eac | ||
|
|
f440f57be2 | ||
|
|
edbdbcf38e | ||
|
|
94a067e34b | ||
|
|
8b6b904cd6 | ||
|
|
27cd8086e1 | ||
|
|
6aa109131e | ||
|
|
74777e54f9 | ||
|
|
61e7450906 | ||
|
|
6a55319e04 | ||
|
|
5692d4e74c | ||
|
|
ffe89d0fe0 | ||
|
|
77e1390686 |
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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'),
|
||||
};
|
||||
|
||||
@@ -85,6 +85,7 @@ fragment MarketFields on Market {
|
||||
open
|
||||
close
|
||||
}
|
||||
successorMarketID
|
||||
}
|
||||
|
||||
query Markets {
|
||||
|
||||
Generated
+320
-8
@@ -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 */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -106,7 +106,6 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
|
||||
if (!connector.current) {
|
||||
throw new Error('No connector');
|
||||
}
|
||||
|
||||
return connector.current.sendTx(pubkey, transaction);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -11,7 +11,6 @@ export enum Status {
|
||||
GettingChainId = 'GettingChainId',
|
||||
Connecting = 'Connecting',
|
||||
GettingPerms = 'GettingPerms',
|
||||
ListingKeys = 'ListingKeys',
|
||||
Connected = 'Connected',
|
||||
Error = 'Error',
|
||||
AcknowledgeNeeded = 'AcknowledgeNeeded',
|
||||
|
||||
Reference in New Issue
Block a user