Compare commits

..
1807 changed files with 47807 additions and 98914 deletions
+3 -3
View File
@@ -51,7 +51,8 @@
"ul": ["list"], "ul": ["list"],
"ol": ["list"] "ol": ["list"]
} }
] ],
"curly": "warn"
} }
}, },
{ {
@@ -74,8 +75,7 @@
"prefer": "type-imports", "prefer": "type-imports",
"disallowTypeAnnotations": true "disallowTypeAnnotations": true
} }
], ]
"curly": ["error", "multi-line"]
} }
}, },
{ {
-45
View File
@@ -1,45 +0,0 @@
---
name: "\U0001F41B Bug"
about: Create a report to help us improve
title: ''
labels: "\U0001F41B bug"
assignees: ''
---
## Description
A clear and concise description of what the bug is.
## Steps to Reproduce
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
## Expected behavior
A clear and concise description of what you expected to happen.
## Screenshots
If applicable, add screenshots to help explain your problem.
## Device details
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
## Additional context
Add any other context about the problem here.
-21
View File
@@ -1,21 +0,0 @@
---
name: Chore
about:
A template to capture non feature / enhancement work we need to do like work
to support other functions or housekeeping etc.
title: ''
labels: chore
assignees: ''
---
## The Chore
What we need to achieve and who for
## Tasks
- [ ] What do we need to do first
- [ ] and then what?
- [ ] Etc.
## Additional details / background info
-33
View File
@@ -1,33 +0,0 @@
---
name: User Story
about:
A template to describe a features or enhancements we want to make from a users
perspective to ensure we understand what is needed and the value it would add
title: ''
labels: feature
assignees: ''
---
## Story
As a
I want
So that
## Acceptance Criteria
- [ ] I can
- [ ] I can
- [ ] I can
## Tasks
- [ ] Explore and sketch
- [ ] Team and stakeholder review
- [ ] Visual Design
- [ ] Team review
- [ ] Etc.
## Sketch
## Additional details / background info
@@ -1,10 +0,0 @@
runs:
using: 'composite'
steps:
- name: Start nomad
shell: bash
run: vegacapsule nomad &
- name: Bootstrap network
shell: bash
run: vegacapsule network bootstrap --config-path=./frontend-monorepo/vegacapsule/config.hcl --force
@@ -1,38 +0,0 @@
inputs:
passphrase:
description: 'Wallet password'
outputs:
token:
description: 'api-token of wallet'
value: ${{ steps.generate-api-token.outputs.api-token }}
runs:
using: 'composite'
steps:
- name: Initialize wallet
shell: bash
run: vega wallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
shell: bash
run: vega wallet import -w capsule_wallet --recovery-phrase-file ./frontend-monorepo/vegacapsule/recovery -p ./frontend-monorepo/vegacapsule/passphrase --home ~/.vegacapsule/testnet/wallet
- name: Generate second public key
shell: bash
run: vega wallet key generate -w capsule_wallet -p ./frontend-monorepo/vegacapsule/passphrase --home ~/.vegacapsule/testnet/wallet
- name: Import network
shell: bash
run: vega wallet network import --force --from-file ./frontend-monorepo/vegacapsule/wallet-config.toml --home ~/.vegacapsule/testnet/wallet
- name: Init api-token
shell: bash
run: vega wallet api-token init --home ~/.vegacapsule/testnet/wallet --passphrase-file ./frontend-monorepo/vegacapsule/passphrase
- name: Generate api-token
id: generate-api-token
shell: bash
run: echo api-token=$(vega wallet api-token generate --wallet-name capsule_wallet --tokens-passphrase-file ./frontend-monorepo/vegacapsule/passphrase --wallet-passphrase-file ./frontend-monorepo/vegacapsule/passphrase --home ~/.vegacapsule/testnet/wallet | grep -Eo '[a-zA-Z0-9]{64}') >> $GITHUB_OUTPUT
- name: Start service using capsule network
shell: bash
run: vega wallet service run -n DV --load-tokens --tokens-passphrase-file ./frontend-monorepo/vegacapsule/passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet &
@@ -1,48 +0,0 @@
inputs:
recovery:
description: 'Recovery phrase'
passphrase:
description: 'Wallet password'
outputs:
token:
description: 'api-token of wallet'
value: ${{ steps.generate-api-token.outputs.api-token }}
runs:
using: 'composite'
steps:
- name: Create passphrase
shell: bash
run: echo "${{ inputs.passphrase }}" > ./passphrase
- name: Create recovery
shell: bash
run: echo "${{ inputs.recovery }}" > ./recovery
- name: Initialize wallet
shell: bash
run: vega wallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
shell: bash
run: vega wallet import -w UI_Trading_Test --recovery-phrase-file ./recovery -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Create public key 2
shell: bash
run: vega wallet key generate -w UI_Trading_Test -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Import network
shell: bash
run: vega wallet network import --from-url="https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml" --force --home ~/.vegacapsule/testnet/wallet
- name: Init api-token
shell: bash
run: vega wallet api-token init --home ~/.vegacapsule/testnet/wallet --passphrase-file passphrase
- name: Generate api-token
id: generate-api-token
shell: bash
run: echo api-token=$(vega wallet api-token generate --wallet-name UI_Trading_Test --tokens-passphrase-file passphrase --wallet-passphrase-file passphrase --home ~/.vegacapsule/testnet/wallet | grep -Eo '[a-zA-Z0-9]{64}') >> $GITHUB_OUTPUT
- name: Start service using stagnet3 network
shell: bash
run: vega wallet service run -n stagnet3 --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet &
+11 -13
View File
@@ -1,27 +1,25 @@
--- ---
name: 'Add Issues To Project Board' name: Auto Assign Issue to New Project
'on': 'on':
issues: issues:
types: types: [opened]
- opened
env:
GH_TOKEN: ${{ secrets.GH_NEW_CARD_TO_PROJECT }}
PROJECT_ID: ${{ secrets.FRONT_END_PROJECT_ID }}
ISSUE_ID: ${{ github.event.issue.node_id }}
USER: ${{ github.actor }}
jobs: jobs:
add_issue: add_issue:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 'Add issue to project board' - name: Add Issue to New Front End Project Board
env:
GITHUB_TOKEN: ${{ secrets.GH_NEW_CARD_TO_PROJECT }}
PROJECT_ID: ${{ secrets.FRONT_END_PROJECT_ID }}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: | run: |
gh api graphql -f query=' gh api graphql -f query='
mutation($user:String!, $project:ID!, $issue:ID!) { mutation($project:ID!, $issue:ID!) {
addProjectV2ItemById(input: {clientMutationId: $user, projectId: $project, contentId: $issue}) { addProjectNextItem(input: {projectId: $project, contentId: $issue}) {
item { projectNextItem {
id id
} }
} }
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID -f user=$USER }' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id'
@@ -1,4 +1,4 @@
name: Cypress tests -- manual trigger name: Capsule tests -- manual trigger
# This workflow runs the frontend tests against chosen branch # This workflow runs the frontend tests against chosen branch
@@ -10,43 +10,146 @@ on:
required: true required: true
type: choice type: choice
options: options:
- console-lite-e2e
- explorer-e2e - explorer-e2e
- liquidity-provision-dashboard-e2e - console-lite-e2e
- stats-e2e - stats-e2e
- token-e2e - token-e2e
- trading-e2e - trading-e2e
tags: includeFlows:
description: 'Test tags to run' description: 'Include long flows (if any)?'
required: true required: true
type: string
default: '@smoke, @regression, @slow'
skip-nx-cache:
description: 'Add --skip-nx-cache to cypress test'
required: false
type: boolean type: boolean
default: false default: true
jobs: jobs:
manual: manual:
name: Run Cypress tests -- manual trigger name: Run capsule tests -- manual trigger
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
GO111MODULE: 'on'
GOPROXY: ${{ secrets.GO_PROXY }}
steps: steps:
- name: Set tags #######
run: echo TAGS="--env.grepTags '[ ${{ github.event.inputs.tags }} ]'" >> $GITHUB_ENV ## Setup langs
#######
- name: Set up Go
uses: actions/setup-go@v3
id: go
with:
go-version: 1.19
- name: Set up Node 16
uses: actions/setup-node@v2
id: npm
with:
node-version: 16
- name: Set --skip-nx-cache flag #######
if: ${{ github.event.inputs.skip-nx-cache == 'true' }} ## Checkout capsule
run: echo SKIP_NX_CACHE="--skip-nx-cache" >> $GITHUB_ENV #######
outputs: # Checkout front ends
skip-cache: ${{env.SKIP_NX_CACHE}} - name: Checkout frontend mono repo
tags: ${{env.TAGS}} uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
path: './frontend-monorepo'
dispatch: # Checkout capsule to build local network
needs: manual - name: Checkout capsule
uses: ./.github/workflows/tests-dispatcher.yml uses: actions/checkout@v2
secrets: inherit with:
with: repository: vegaprotocol/vegacapsule
project: ${{ inputs.project }} ref: main
skip-cache: ${{needs.manual.outputs.skip-cache}} token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
tags: ${{needs.manual.outputs.tags}} path: './capsule'
#######
## Build binaries
#######
- name: Build capsule
run: go install
working-directory: capsule
- name: Set GOBIN
run: echo GOBIN=$(go env GOPATH)/bin >> $GITHUB_ENV
- name: Checkout Vega
uses: actions/checkout@v2
with:
repository: vegaprotocol/vega
ref: v0.54.0
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
path: './vega'
- name: Install Vega binaries
run: go install -v ./cmd/vega
working-directory: vega
- name: Install Vega wallet binaries
run: go install ./cmd/vegawallet
working-directory: vega
- name: Install date-node binaries
run: go install ./cmd/data-node
working-directory: vega
######
## Start capsule
######
- name: Login to docker
run: echo -n ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }} | docker login https://ghcr.io -u vega-ci-bot --password-stdin
- name: Start nomad
run: vegacapsule nomad &
- name: Bootstrap network
run: vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl --force
working-directory: capsule
######
## Setup a Vega wallet for our user
######
- name: Create passphrase
run: echo "${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}" > ./passphrase
- name: Create recovery
run: echo "${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}" > ./recovery
- name: Initialize wallet
run: vegawallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
run: vegawallet import -w UI_Trading_Test --recovery-phrase-file ./recovery -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Create public key 2
run: vegawallet key generate -w UI_Trading_Test -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Start service
run: vegawallet service run --network DV --automatic-consent --home ~/.vegacapsule/testnet/wallet &
######
## Run some tests
######
- name: Install root dependencies
run: yarn install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: yarn nx run ${{ github.event.inputs.project }}:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPESS_ETH_WALLET_MNEMONIC }}
CYPRESS_INCLUDE_FLOWS: ${{ github.event.inputs.includeFlows }}
######
## Upload logs
######
- name: Logs
if: ${{ always() }}
run: vegacapsule network logs > vega-capsule-logs.txt
- uses: actions/upload-artifact@v2
with:
name: logs
path: ./vega-capsule-logs.txt
+131 -8
View File
@@ -1,17 +1,140 @@
name: Cypress tests -- night run name: Capsule tests -- night run
# This workflow runs the frontend tests against latest develop of the core to preempt breaking changes # This workflow runs the frontend tests against latest develop of the core to preempt breaking changes
on: on:
schedule: schedule:
- cron: '0 4 * * *' - cron: '0 4 * * *'
workflow_dispatch:
jobs: jobs:
nightly: nightly:
uses: ./.github/workflows/tests-dispatcher.yml name: Run capsule tests -- nightly
secrets: inherit runs-on: ubuntu-latest
with: env:
project: '[console-lite-e2e, explorer-e2e, liquidity-provision-dashboard-e2e, stats-e2e, token-e2e, trading-e2e]' GO111MODULE: 'on'
tags: --env.grepTags '[ @smoke, @regression, @slow ]' steps:
night-run: true #######
## Setup langs
#######
- name: Set up Go
uses: actions/setup-go@v3
id: go
with:
go-version: 1.19
- name: Set up Node 16
uses: actions/setup-node@v2
id: npm
with:
node-version: 16
#######
## Checkout capsule
#######
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
path: './frontend-monorepo'
# Checkout capsule to build local network
- name: Checkout capsule
uses: actions/checkout@v2
with:
repository: vegaprotocol/vegacapsule
ref: main
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
path: './capsule'
#######
## Build binaries
#######
- name: Build capsule
run: go install
working-directory: capsule
- name: Set GOBIN
run: echo GOBIN=$(go env GOPATH)/bin >> $GITHUB_ENV
- name: Checkout Vega
uses: actions/checkout@v2
with:
repository: vegaprotocol/vega
ref: v0.54.0
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
path: './vega'
- name: Install Vega binaries
run: go install -v ./cmd/vega
working-directory: vega
- name: Install Vega wallet binaries
run: go install ./cmd/vegawallet
working-directory: vega
- name: Install date-node binaries
run: go install ./cmd/data-node
working-directory: vega
######
## Start capsule
######
- name: Login to docker
run: echo -n ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }} | docker login https://ghcr.io -u vega-ci-bot --password-stdin
- name: Start nomad
run: vegacapsule nomad &
- name: Bootstrap network
run: vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl --force
working-directory: capsule
######
## Setup a Vega wallet for our user
######
- name: Create passphrase
run: echo "${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}" > ./passphrase
- name: Create recovery
run: echo "${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}" > ./recovery
- name: Initialize wallet
run: vegawallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
run: vegawallet import -w UI_Trading_Test --recovery-phrase-file ./recovery -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Create public key 2
run: vegawallet key generate -w UI_Trading_Test -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Start service
run: vegawallet service run --network DV --automatic-consent --home ~/.vegacapsule/testnet/wallet &
######
## Run some tests
######
- name: Install root dependencies
run: yarn install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: yarn nx run-many --target=e2e --all --record --key ${{ secrets.CYPRESS_RECORD_KEY }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPESS_ETH_WALLET_MNEMONIC }}
CYPRESS_NIGHTLY_RUN: true
CYPRESS_INCLUDE_FLOWS: true
######
## Upload logs
######
- name: Logs
if: ${{ always() }}
run: vegacapsule network logs > vega-capsule-logs.txt
- uses: actions/upload-artifact@v2
with:
name: logs
path: ./vega-capsule-logs.txt
+120 -28
View File
@@ -1,61 +1,153 @@
name: Cypress tests - PR name: Capsule tests
on: on:
push: push:
branches: branches:
- master
- develop - develop
- main
pull_request: pull_request:
types: types:
- opened - opened
- reopened - reopened
- synchronize - synchronize
- ready_for_review - ready_for_review
jobs: jobs:
pr: pr:
name: Run capsule tests - PR
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == false }}
env:
GO111MODULE: 'on'
steps: steps:
#######
## Setup langs
#######
- name: Set up Go
uses: actions/setup-go@v3
id: go
with:
go-version: 1.19
- name: Set up Node 16
uses: actions/setup-node@v2
id: npm
with:
node-version: 16
#######
## Checkout capsule
#######
# Checkout front ends # Checkout front ends
- name: Checkout frontend mono repo - name: Checkout frontend mono repo
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0 fetch-depth: 0
path: './frontend-monorepo' path: './frontend-monorepo'
# Restore node_modules from cache if possible # Checkout capsule to build local network
- name: Restore node_modules from cache - name: Checkout capsule
uses: actions/cache@v3 uses: actions/checkout@v2
with: with:
path: | repository: vegaprotocol/vegacapsule
frontend-monorepo/node_modules ref: main
/home/runner/.cache/Cypress token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }} path: './capsule'
# Install frontend dependencies #######
- name: Install root dependencies ## Build binaries
run: yarn install --frozen-lockfile #######
working-directory: frontend-monorepo - name: Build capsule
run: go install
working-directory: capsule
- name: Set GOBIN
run: echo GOBIN=$(go env GOPATH)/bin >> $GITHUB_ENV
- name: Checkout Vega
uses: actions/checkout@v2
with:
repository: vegaprotocol/vega
ref: v0.54.0
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
path: './vega'
- name: Install Vega binaries
run: go install -v ./cmd/vega
working-directory: vega
- name: Install Vega wallet binaries
run: go install ./cmd/vegawallet
working-directory: vega
- name: Install date-node binaries
run: go install ./cmd/data-node
working-directory: vega
######
## Start capsule
######
- name: Login to docker
run: echo -n ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }} | docker login https://ghcr.io -u vega-ci-bot --password-stdin
- name: Start nomad
run: vegacapsule nomad &
- name: Bootstrap network
run: vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl --force
working-directory: capsule
######
## Setup a Vega wallet for our user
######
- name: Create passphrase
run: echo "${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}" > ./passphrase
- name: Create recovery
run: echo "${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}" > ./recovery
- name: Initialize wallet
run: vegawallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
run: vegawallet import -w UI_Trading_Test --recovery-phrase-file ./recovery -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Create public key 2
run: vegawallet key generate -w UI_Trading_Test -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Start service
run: vegawallet service run --network DV --automatic-consent --home ~/.vegacapsule/testnet/wallet &
######
## Run some tests
######
# Check SHAs
- name: Derive appropriate SHAs for base and head for `nx affected` commands - name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v2 uses: nrwl/nx-set-shas@v2
with: with:
working-directory: frontend-monorepo working-directory: frontend-monorepo
main-branch-name: ${{ github.base_ref || github.ref_name }} main-branch-name: master
set-environment-variables-for-job: true
# See affected projects - name: Install root dependencies
- name: See affected apps run: yarn install
run: echo AFFECTED=$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects) >> $GITHUB_ENV
working-directory: frontend-monorepo working-directory: frontend-monorepo
outputs: - name: Run Cypress tests
projects: ${{ env.AFFECTED }} run: npx nx affected:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPESS_ETH_WALLET_MNEMONIC }}
CYPRESS_INCLUDE_FLOWS: false
dispatch: ######
needs: pr ## Upload logs
uses: ./.github/workflows/tests-dispatcher.yml ######
secrets: inherit
with: - name: Logs
project: ${{ needs.pr.outputs.projects }} if: ${{ always() }}
tags: "--env.grepTags='[ @smoke, @regression ]'" run: vegacapsule network logs > vega-capsule-logs.txt
- uses: actions/upload-artifact@v2
if: ${{ always() }}
with:
name: logs
path: ./vega-capsule-logs.txt
@@ -1,69 +0,0 @@
name: Cypress - console-lite
on:
workflow_call:
inputs:
trigger:
required: true
type: string
default: 'false'
skip-cache:
required: false
type: string
tags:
required: false
type: string
jobs:
console-lite-e2e:
if: ${{ inputs.trigger == 'true' }}
runs-on: self-hosted
steps:
- name: Vega version
run: vega version
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
######
## Setup a Vega wallet for our user
######
- name: Set up Vegawallet
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet
with:
recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run console-lite-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
-106
View File
@@ -1,106 +0,0 @@
name: Cypress - explorer
on:
workflow_call:
inputs:
trigger:
required: true
type: string
default: 'false'
skip-cache:
required: false
type: string
tags:
required: false
type: string
night-run:
required: false
type: boolean
default: false
jobs:
explorer-e2e:
if: ${{ inputs.trigger == 'true' }}
runs-on: self-hosted
timeout-minutes: 30
steps:
- name: Vega version
run: vega version
- name: Vegacapsule version
run: vegacapsule version
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
#######
## Build and run Vegacapsule network
#######
- name: Build and run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
######
## Setup a Vega wallet for our user
######
- name: Set up Vegawallet for docker
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
with:
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
######
## Run some tests
######
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run explorer-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_NIGHTLY_RUN: ${{ inputs.night-run }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
######
## Upload logs
######
# Artifact path is not valid: /ganache-1/capsule-logscolletor.stderr-2022-12-22T10:20:30Z.log. Contains the following character: Colon :
- name: Rename files to allow archive
if: ${{ always() }}
run: |
while read -r file; do
mv "${file}" "$(echo ${file} | sed 's|:|-|g')"
done< <(find /home/runner/.vegacapsule/testnet/logs -type f)
- uses: actions/upload-artifact@v2
if: ${{ always() }}
with:
name: logs
path: /home/runner/.vegacapsule/testnet/logs
@@ -1,47 +0,0 @@
name: Cypress - liquidity provision dashboard
on:
workflow_call:
inputs:
trigger:
required: true
type: string
default: 'false'
jobs:
liquidity-provision-dashboard-e2e:
timeout-minutes: 10
if: ${{ inputs.trigger == 'true' }}
runs-on: self-hosted
steps:
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run liquidity-provision-dashboard-e2e:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
-31
View File
@@ -1,31 +0,0 @@
name: Cypress Console tests -- live environment
# This workflow runs using provided url
on:
workflow_dispatch:
inputs:
url:
description: 'Url'
required: true
type: string
jobs:
cypress-run:
name: Run Cypress Trading tests -- live environment
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Run Cypress tests
uses: cypress-io/github-action@v4
with:
browser: chrome
record: true
project: ./apps/trading-e2e
config: baseUrl=${{ github.event.inputs.url }}
env: grepTags=@live
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-47
View File
@@ -1,47 +0,0 @@
name: Cypress - stats
on:
workflow_call:
inputs:
trigger:
required: true
type: string
default: 'false'
jobs:
stats-e2e:
runs-on: self-hosted
if: ${{ inputs.trigger == 'true' }}
timeout-minutes: 10
steps:
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run stats-e2e:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
-101
View File
@@ -1,101 +0,0 @@
name: Cypress - token
on:
workflow_call:
inputs:
trigger:
required: true
type: string
default: 'false'
skip-cache:
required: false
type: string
tags:
required: false
type: string
jobs:
token-e2e:
if: ${{ inputs.trigger == 'true' }}
runs-on: self-hosted
timeout-minutes: 60
steps:
- name: Vega version
run: vega version
- name: Vegacapsule version
run: vegacapsule version
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
#######
## Build and run Vegacapsule network
#######
- name: Build and run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
######
## Setup a Vega wallet for our user
######
- name: Set up Vegawallet for docker
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
with:
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
######
## Run some tests
######
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run token-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
######
## Upload logs
######
# Artifact path is not valid: /ganache-1/capsule-logscolletor.stderr-2022-12-22T10:20:30Z.log. Contains the following character: Colon :
- name: Rename files to allow archive
if: ${{ always() }}
run: |
while read -r file; do
mv "${file}" "$(echo ${file} | sed 's|:|-|g')"
done< <(find /home/runner/.vegacapsule/testnet/logs -type f)
- uses: actions/upload-artifact@v2
if: ${{ always() }}
with:
name: logs
path: /home/runner/.vegacapsule/testnet/logs
-70
View File
@@ -1,70 +0,0 @@
name: Cypress - trading
on:
workflow_call:
inputs:
trigger:
required: true
type: string
default: 'false'
skip-cache:
required: false
type: string
tags:
required: false
type: string
jobs:
trading-e2e:
if: ${{ inputs.trigger == 'true' }}
timeout-minutes: 30
runs-on: self-hosted
steps:
- name: Vega version
run: vega version
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
######
## Setup a Vega wallet for our user
######
- name: Set up Vegawallet
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet
with:
recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run trading-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
+1 -1
View File
@@ -3,7 +3,7 @@ name: Generate queries
on: on:
push: push:
branches: branches:
- develop - master
jobs: jobs:
master: master:
+32
View File
@@ -0,0 +1,32 @@
---
name: Deploy affected projects to IPFS
'on':
# Triggers the workflow on push to main branch
push:
branches: [master]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch: null
jobs:
master:
name: Publish NX Affected
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.14.0
- name: Install root dependencies
run: yarn install
- name: Run deploy for affected sites
env:
FLEEK_API_KEY: ${{ secrets.FLEEK_API_KEY }}
run: ./tools/ipfs-deploy.js
-21
View File
@@ -1,21 +0,0 @@
---
name: Verify PR title
on:
pull_request:
types: [opened, ready_for_review, reopened, edited, synchronize]
jobs:
lint_pr:
runs-on: ubuntu-latest
steps:
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.14.0
- name: Install commitlint cli and config
run: npm install @commitlint/cli @commitlint/config-conventional
- name: Create config
run: echo "module.exports = {extends:['@commitlint/config-conventional']};" > commitlint.config.js
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint
@@ -4,7 +4,7 @@ name: Publish docker containers
push: push:
tags: tags:
- 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-*' - 'v[0-9]+.[0-9]+.[0-9]+-pre[0-9]+'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
+39 -12
View File
@@ -3,37 +3,66 @@ name: Unit tests & build
on: on:
push: push:
branches: branches:
- master
- develop - develop
- main
pull_request: pull_request:
jobs: jobs:
pr: master:
name: Test and lint - PR name: Test and lint - main
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: 'read' contents: 'read'
actions: 'read' actions: 'read'
if: ${{ github.event_name != 'pull_request' }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0 fetch-depth: 0
- name: Derive appropriate SHAs for base and head for `nx affected` commands - name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v2 uses: nrwl/nx-set-shas@v2
with: with:
main-branch-name: ${{ github.base_ref }} main-branch-name: master
- name: Use Node.js 16 - name: Use Node.js 16
id: Node id: Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16.14.0 node-version: 16.14.0
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: node_modules-${{ hashFiles('**/yarn.lock') }}
- name: Install root dependencies - name: Install root dependencies
run: yarn install --frozen-lockfile run: yarn install
- name: Check formatting
run: yarn nx format:check
- name: Lint affected
run: yarn nx affected:lint --max-warnings=0
- name: Test affected
run: yarn nx affected:test
- name: Build affected
run: yarn nx affected:build
pr:
name: Test and lint - PR
runs-on: ubuntu-latest
permissions:
contents: 'read'
actions: 'read'
if: ${{ github.event_name == 'pull_request' }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v2
with:
main-branch-name: master
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v3
with:
node-version: 16.14.0
- name: Install root dependencies
run: yarn install
- name: Check formatting - name: Check formatting
run: yarn nx format:check run: yarn nx format:check
- name: Lint affected - name: Lint affected
@@ -42,5 +71,3 @@ jobs:
run: yarn nx affected:test run: yarn nx affected:test
- name: Build affected - name: Build affected
run: yarn nx affected:build run: yarn nx affected:build
- name: Build affected spec
run: yarn nx affected --target=build-spec
-61
View File
@@ -1,61 +0,0 @@
on:
workflow_call:
inputs:
project:
required: true
type: string
skip-cache:
required: false
type: string
tags:
required: false
type: string
night-run:
required: false
type: boolean
jobs:
run-console-lite-e2e:
uses: ./.github/workflows/cypress-console-lite-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'console-lite-e2e') || contains(inputs.project, 'console-lite') }}
skip-cache: ${{ inputs.skip-cache }}
tags: ${{ inputs.tags }}
run-explorer-e2e:
uses: ./.github/workflows/cypress-explorer-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'explorer-e2e') || contains(inputs.project, 'explorer') }}
skip-cache: ${{ inputs.skip-cache }}
tags: ${{ inputs.tags }}
night-run: ${{ inputs.night-run }}
run-liquidity-e2e:
uses: ./.github/workflows/cypress-liquidity-provision-dashboard-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'liquidity-provision-dashboard-e2e') || contains(inputs.project, 'liquidity-provision-dashboard') }}
run-stats-e2e:
uses: ./.github/workflows/cypress-stats-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'stats-e2e') || contains(inputs.project, 'stats') }}
run-token-e2e:
uses: ./.github/workflows/cypress-token-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'token-e2e') || contains(inputs.project, 'token') }}
skip-cache: ${{ inputs.skip-cache }}
tags: ${{ inputs.tags }}
run-trading-e2e:
uses: ./.github/workflows/cypress-trading-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'trading-e2e') || contains(inputs.project, 'trading') }}
skip-cache: ${{ inputs.skip-cache }}
tags: ${{ inputs.tags }}
+1 -19
View File
@@ -1,24 +1,6 @@
module.exports = { module.exports = {
stories: [], stories: [],
addons: [ addons: ['@storybook/addon-essentials', '@storybook/addon-a11y'],
'@storybook/addon-actions',
'@storybook/addon-viewport',
{
name: '@storybook/addon-docs',
options: {
configureJSX: true,
babelOptions: {},
sourceLoaderOptions: null,
transcludeMarkdown: true,
},
},
'@storybook/addon-controls',
'@storybook/addon-backgrounds',
'@storybook/addon-toolbars',
'@storybook/addon-measure',
'@storybook/addon-outline',
'@storybook/addon-a11y',
],
// uncomment the property below if you want to apply some webpack config globally // uncomment the property below if you want to apply some webpack config globally
// webpackFinal: async (config, { configType }) => { // webpackFinal: async (config, { configType }) => {
// // Make whatever fine-grained changes you need that should apply to all storybook configs // // Make whatever fine-grained changes you need that should apply to all storybook configs
-1
View File
@@ -1,2 +1 @@
* @vegaprotocol/frontend * @vegaprotocol/frontend
*.graphql @vegaprotocol/core
Vendored
-20
View File
@@ -1,20 +0,0 @@
@Library('vega-shared-library') _
def commitHash = 'UNKNOWN'
pipeline {
agent any
options {
skipDefaultCheckout true
parallelsAlwaysFailFast()
}
stages {
stage('approbation') {
steps {
sh 'printenv'
checkout scm
runApprobation ignoreFailure: false, frontendBranch: env.BRANCH_NAME, type: 'frontend'
}
}
}
}
+5 -27
View File
@@ -30,10 +30,6 @@ An application for the status of the Vega network. Showing block height and othe
Hosting for static content being shared across apps, for example fonts. Hosting for static content being shared across apps, for example fonts.
### [Multisig-signer](./apps/multisig-signer)
The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract.
# 🧱 Libraries in this repo # 🧱 Libraries in this repo
### [UI toolkit](./libs/ui-toolkit) ### [UI toolkit](./libs/ui-toolkit)
@@ -86,14 +82,13 @@ Run `yarn nx run <my-app>-e2e:e2e` to execute the e2e tests with [cypress](https
Run `nx test my-app` to execute the unit tests with [Jest](https://jestjs.io), or `nx affected:test` to execute just unit tests affected by a change. You can also use `--watch` with these test to run jest in watch mode, see [Jest executor](https://nx.dev/packages/jest/executors/jest) for all CLI flags. Run `nx test my-app` to execute the unit tests with [Jest](https://jestjs.io), or `nx affected:test` to execute just unit tests affected by a change. You can also use `--watch` with these test to run jest in watch mode, see [Jest executor](https://nx.dev/packages/jest/executors/jest) for all CLI flags.
### Using wallet #### Trading app E2E tests
To run tests locally using your own wallets make sure you have generated at least two public keys and update the following environment variables in `cypress.config.js` to match your wallet. To run tests locally using your own wallets you can add the following environment variables to `cypress.json`
1. Set `VEGA_PUBLIC_KEY` and `TRUNCATED_VEGA_PUBLIC_KEY` to your first public key. 1. Change `TRADING_TEST_VEGA_WALLET_NAME` to your Vega wallet name
2. Set `VEGA_PUBLIC_KEY2` and `TRUNCATED_VEGA_PUBLIC_KEY2` to your second public key. 2. Add `TRADING_TEST_VEGA_WALLET_PASSPHRASE` as your wallet passphrase
3. Set `TRADING_TEST_VEGA_WALLET_PASSPHRASE` as your wallet passphrase 3. Add `ETH_WALLET_MNEMONIC` as your Ethereum wallet mnemonic
4. Add `ETH_WALLET_MNEMONIC` as your Ethereum wallet mnemonic
### Formatting ### Formatting
@@ -156,23 +151,6 @@ In order to run the bootstrap command to generate and start a new network, we mu
vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl
``` ```
In order to setup and run vegawallet for e2e capsule tests, in a separate terminal window:
1. cd into `./vegacapsule`
2. run:
```bash
bash setup-vegawallet.sh
```
3. copy generated `api-token` and paste the token into `CYPRESS_VEGA_WALLET_API_TOKEN` environment variable in either `apps/token-e2e/.env` or `apps/explorer-e2e/.env` depending on which project needs testing.
Note: The script is only needed if capsule was built for first time or fresh. To run existing wallet service for capsule:
```bash
vega wallet service run -n DV --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet
```
# 📑 License # 📑 License
[MIT](./LICENSE) [MIT](./LICENSE)
+27 -10
View File
@@ -1,10 +1,27 @@
# App configuration variables # React Environment Variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql # https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env
NX_VEGA_ENV=STAGNET3
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 # Netlify Environment Variables
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz # https://www.netlify.com/docs/continuous-deployment/#environment-variables
NX_VEGA_WALLET_URL=http://localhost:1789
CYPRESS_VEGA_ENV=STAGNET3 NX_VERSION=$npm_package_version
CYPRESS_VEGA_WALLET_API_TOKEN= NX_REPOSITORY_URL=$REPOSITORY_URL
NX_BRANCH=$BRANCH
NX_PULL_REQUEST=$PULL_REQUEST
NX_HEAD=$HEAD
NX_COMMIT_REF=$COMMIT_REF
NX_CONTEXT=$CONTEXT
NX_REVIEW_ID=$REVIEW_ID
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
NX_URL=$URL
NX_DEPLOY_URL=$DEPLOY_URL
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
NX_VEGA_URL=https://api.n11.testnet.vega.xyz/graphql
NX_VEGA_ENV=TESTNET
NX_VEGA_REST=https://api.n11.testnet.vega.xyz
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
+5 -11
View File
@@ -3,18 +3,14 @@ module.exports = defineConfig({
projectId: 'et4snf', projectId: 'et4snf',
e2e: { e2e: {
setupNodeEvents(on, config) {
require('@cypress/grep/src/plugin')(config);
return config;
},
baseUrl: 'http://localhost:4200', baseUrl: 'http://localhost:4200',
fileServerFolder: '.', fileServerFolder: '.',
fixturesFolder: false, fixturesFolder: false,
specPattern: './src/integration/*.ts', specPattern: './src/integration/*.ts',
excludeSpecPattern: '**/*.js', excludeSpecPattern: '**/*.js',
modifyObstructiveCode: false, modifyObstructiveCode: false,
supportFile: './src/support/index.js', supportFile: './src/support/index.ts',
video: false, video: true,
videoUploadOnPasses: false, videoUploadOnPasses: false,
videosFolder: '../../dist/cypress/apps/console-lite-e2e/videos', videosFolder: '../../dist/cypress/apps/console-lite-e2e/videos',
screenshotsFolder: '../../dist/cypress/apps/console-lite-e2e/screenshots', screenshotsFolder: '../../dist/cypress/apps/console-lite-e2e/screenshots',
@@ -23,8 +19,9 @@ module.exports = defineConfig({
viewportHeight: 900, viewportHeight: 900,
}, },
env: { env: {
TRADING_TEST_VEGA_WALLET_NAME: 'UI_Trading_Test',
ETHEREUM_PROVIDER_URL: ETHEREUM_PROVIDER_URL:
'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8', 'https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
VEGA_PUBLIC_KEY: VEGA_PUBLIC_KEY:
'47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278', '47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278',
VEGA_PUBLIC_KEY2: VEGA_PUBLIC_KEY2:
@@ -32,14 +29,11 @@ module.exports = defineConfig({
TRUNCATED_VEGA_PUBLIC_KEY: '47836c…c7d278', TRUNCATED_VEGA_PUBLIC_KEY: '47836c…c7d278',
TRUNCATED_VEGA_PUBLIC_KEY2: '1a18cd…0cf2e4', TRUNCATED_VEGA_PUBLIC_KEY2: '1a18cd…0cf2e4',
ETHEREUM_WALLET_ADDRESS: '0x265Cc6d39a1B53d0d92068443009eE7410807158', ETHEREUM_WALLET_ADDRESS: '0x265Cc6d39a1B53d0d92068443009eE7410807158',
ETHERSCAN_URL: 'https://sepolia.etherscan.io', ETHERSCAN_URL: 'https://ropsten.etherscan.io',
tsConfig: 'tsconfig.json', tsConfig: 'tsconfig.json',
TAGS: 'not @todo and not @ignore and not @manual', TAGS: 'not @todo and not @ignore and not @manual',
TRADING_TEST_VEGA_WALLET_PASSPHRASE: '123', TRADING_TEST_VEGA_WALLET_PASSPHRASE: '123',
ETH_WALLET_MNEMONIC: ETH_WALLET_MNEMONIC:
'ugly gallery notice network true range brave clarify flat logic someone chunk', 'ugly gallery notice network true range brave clarify flat logic someone chunk',
grepTags: '@regression @smoke @slow',
grepFilterSpecs: true,
grepOmitFiltered: true,
}, },
}); });
-1
View File
@@ -1 +0,0 @@
declare module '*.scss';
-20
View File
@@ -1,20 +0,0 @@
/// <reference types="cypress" />
declare namespace Cypress {
// specify additional properties in the TestConfig object
// in our case we will add "tags" property
interface SuiteConfigOverrides {
/**
* List of tags for this test
* @example a single tag
* it('logs in', { tags: '@smoke' }, () => { ... })
* @example multiple tags
* it('works', { tags: ['@smoke', '@slow'] }, () => { ... })
*/
tags?: string | string[];
}
interface Cypress {
grep?: (grep?: string, tags?: string, burn?: string) => void;
}
}
@@ -1,8 +1,5 @@
describe('simple trading app', { tags: '@smoke' }, () => { describe('simple trading app', () => {
beforeEach(() => { beforeEach(() => cy.visit('/'));
cy.mockConsole();
cy.visit('/');
});
it('render', () => { it('render', () => {
cy.get('#root').should('exist'); cy.get('#root').should('exist');
@@ -1,4 +1,4 @@
describe('console lite header', { tags: '@smoke' }, () => { describe('console lite header', () => {
beforeEach(() => { beforeEach(() => {
window.localStorage.setItem('theme', 'dark'); window.localStorage.setItem('theme', 'dark');
cy.visit('/'); cy.visit('/');
@@ -1,11 +1,15 @@
import { aliasGQLQuery } from '@vegaprotocol/cypress'; import { aliasQuery } from '@vegaprotocol/cypress';
import type { MarketsQuery } from '@vegaprotocol/market-list'; import {
import { marketCandlesQuery, marketsQuery } from '@vegaprotocol/mock'; generateLongListMarkets,
generateSimpleMarkets,
} from '../support/mocks/generate-markets';
describe('market list', { tags: '@smoke' }, () => { describe('market list', () => {
describe('simple url', () => { describe('simple url', () => {
beforeEach(() => { beforeEach(() => {
cy.mockConsole(); cy.mockGQL((req) => {
aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets());
});
cy.visit('/markets'); cy.visit('/markets');
}); });
@@ -57,7 +61,9 @@ describe('market list', { tags: '@smoke' }, () => {
describe('url params should select filters', () => { describe('url params should select filters', () => {
beforeEach(() => { beforeEach(() => {
cy.mockConsole(); cy.mockGQL((req) => {
aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets());
});
}); });
it('suspended status', () => { it('suspended status', () => {
@@ -67,11 +73,10 @@ describe('market list', { tags: '@smoke' }, () => {
it('last asset (if exists)', () => { it('last asset (if exists)', () => {
cy.visit('/markets'); cy.visit('/markets');
cy.wait('@Markets').then((filters) => { cy.wait('@SimpleMarkets').then((filters) => {
const data: MarketsQuery | undefined = filters?.response?.body?.data; if (filters?.response?.body?.data?.markets?.length) {
if (data?.marketsConnection?.edges.length) {
const asset = const asset =
data?.marketsConnection?.edges[0].node.tradableInstrument.instrument filters.response.body.data.markets[0].tradableInstrument.instrument
.product.settlementAsset.symbol; .product.settlementAsset.symbol;
cy.visit(`/markets/Suspended/Future/${asset}`); cy.visit(`/markets/Suspended/Future/${asset}`);
cy.getByTestId('market-assets-menu') cy.getByTestId('market-assets-menu')
@@ -90,26 +95,13 @@ describe('market list', { tags: '@smoke' }, () => {
}); });
describe('long list of results should be handled properly', () => { describe('long list of results should be handled properly', () => {
beforeEach(() => { it('handles 1000 markets', () => {
cy.mockConsole();
cy.viewport(1440, 900); cy.viewport(1440, 900);
const market = marketsQuery().marketsConnection?.edges[0];
const edges = new Array(1000)
.fill('')
.map(() => Object.assign({}, market));
cy.mockGQL((req) => { cy.mockGQL((req) => {
aliasGQLQuery( aliasQuery(req, 'SimpleMarkets', generateLongListMarkets(1000));
req,
'Markets',
marketsQuery({ marketsConnection: { edges } })
);
aliasGQLQuery(req, 'MarketsCandles', marketCandlesQuery());
}); });
performance.mark('start-1k'); performance.mark('start-1k');
cy.visit('/markets'); cy.visit('/markets');
cy.wait('@Markets');
});
it('handles 1000 markets', () => {
cy.get('.ag-center-cols-container', { timeout: 50000 }).then(() => { cy.get('.ag-center-cols-container', { timeout: 50000 }).then(() => {
performance.mark('end-1k'); performance.mark('end-1k');
performance.measure('load-1k', 'start-1k', 'end-1k'); performance.measure('load-1k', 'start-1k', 'end-1k');
@@ -1,84 +1,138 @@
const marketId = 'market-0'; import { connectVegaWallet } from '../support/connect-wallet';
const marketName = 'ACTIVE MARKET'; import { aliasQuery } from '@vegaprotocol/cypress';
import { generateSimpleMarkets } from '../support/mocks/generate-markets';
import { generateDealTicket } from '../support/mocks/generate-deal-ticket';
import { generateMarketTags } from '../support/mocks/generate-market-tags';
import { generateMarketPositions } from '../support/mocks/generate-market-positions';
import { generateEstimateOrder } from '../support/mocks/generate-estimate-order';
import { generatePartyBalance } from '../support/mocks/generate-party-balance';
import { generatePartyMarketData } from '../support/mocks/generate-party-market-data';
import { generateMarketMarkPrice } from '../support/mocks/generate-market-mark-price';
import { generateMarketNames } from '../support/mocks/generate-market-names';
describe('market selector', { tags: '@smoke' }, () => { describe('market selector', () => {
let markets;
beforeEach(() => { beforeEach(() => {
cy.mockConsole(); cy.mockGQL((req) => {
cy.visit(`/trading/${marketId}`); aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets());
cy.connectVegaWallet(); aliasQuery(req, 'DealTicketQuery', generateDealTicket());
cy.wait('@Markets'); aliasQuery(req, 'MarketTags', generateMarketTags());
aliasQuery(req, 'MarketPositions', generateMarketPositions());
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
aliasQuery(req, 'PartyBalanceQuery', generatePartyBalance());
aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
aliasQuery(req, 'MarketNames', generateMarketNames());
});
cy.visit('/markets');
cy.wait('@SimpleMarkets').then((response) => {
if (response.response.body.data?.markets?.length) {
markets = response.response.body.data.markets;
}
});
}); });
it('should be properly rendered', () => { it('should be properly rendered', () => {
cy.get('input[placeholder="Search"]').should('have.value', marketName); if (markets?.length) {
cy.getByTestId('arrow-button').click(); cy.visit(`/trading/${markets[0].id}`);
cy.getByTestId('market-pane').should('be.visible'); connectVegaWallet();
cy.getByTestId('market-pane') cy.get('input[placeholder="Search"]').should(
.children() 'have.value',
.find('[role="button"]') markets[0].tradableInstrument.instrument.name
.first() );
.should('contain.text', marketName); cy.getByTestId('arrow-button').click();
cy.getByTestId('market-pane') cy.getByTestId('market-pane').should('be.visible');
.children() cy.getByTestId('market-pane')
.find('[role="button"]') .children()
.first() .find('[role="button"]')
.click(); .first()
cy.getByTestId('market-pane').should('not.be.visible'); .should('contain.text', markets[0].tradableInstrument.instrument.name);
cy.getByTestId('market-pane')
.children()
.find('[role="button"]')
.first()
.click();
cy.getByTestId('market-pane').should('not.be.visible');
}
}); });
it('typing should change list', () => { it('typing should change list', () => {
cy.get('input[placeholder="Search"]').type('{backspace}'); if (markets?.length) {
cy.getByTestId('market-pane') cy.visit(`/trading/${markets[0].id}`);
.children() connectVegaWallet();
.find('[role="button"]') cy.get('input[placeholder="Search"]').type('{backspace}');
.should('have.length.at.least', 1); cy.getByTestId('market-pane')
cy.get('input[placeholder="Search"]').clear(); .children()
cy.get('input[placeholder="Search"]').type('ACTIVE'); .find('[role="button"]')
cy.getByTestId('market-pane') .should('have.length.at.least', 1);
.children() cy.get('input[placeholder="Search"]').clear();
.find('[role="button"]') cy.get('input[placeholder="Search"]').type('aa');
.should('have.length', 1); const filtered = markets.filter(
cy.getByTestId('market-pane') (market) =>
.children() market.state === 'STATE_ACTIVE' &&
.find('[role="button"]') market.tradableInstrument.instrument.name.match(/aa/i)
.last() );
.click(); cy.getByTestId('market-pane')
cy.location('pathname').should('eq', `/trading/${marketId}`); .children()
cy.get('input[placeholder="Search"]').should('have.value', marketName); .find('[role="button"]')
.should('have.length', filtered.length);
cy.getByTestId('market-pane')
.children()
.find('[role="button"]')
.last()
.click();
cy.location('pathname').should(
'eq',
`/trading/${filtered[filtered.length - 1].id}`
);
cy.get('input[placeholder="Search"]').should(
'have.value',
filtered[filtered.length - 1].tradableInstrument.instrument.name
);
}
}); });
// constantly failing on ci // constantly failing on ci
it.skip('keyboard navigation should work well', () => { it.skip('keyboard navigation should work well', () => {
cy.get('input[placeholder="Search"]').type('{backspace}'); if (markets?.length) {
cy.get('input[placeholder="Search"]').clear(); cy.visit(`/trading/${markets[0].id}`);
cy.focused().realPress('ArrowDown'); connectVegaWallet();
cy.focused().should('contain.text', marketName); cy.get('input[placeholder="Search"]').type('{backspace}');
cy.focused().realPress('ArrowDown'); cy.get('input[placeholder="Search"]').clear();
cy.focused().should('contain.text', 'ETHBTC').realPress('Enter'); cy.focused().realPress('ArrowDown');
cy.location('pathname').should('eq', '/trading/ethbtc-quaterly'); cy.focused().should('contain.text', 'AAVEDAI Monthly');
cy.focused().realPress('ArrowDown');
cy.focused().should('contain.text', 'ETHBTC').realPress('Enter');
cy.location('pathname').should('eq', '/trading/ethbtc-quaterly');
cy.get('input[placeholder="Search"]').type('{backspace}'); cy.get('input[placeholder="Search"]').type('{backspace}');
cy.get('input[placeholder="Search"]').clear(); cy.get('input[placeholder="Search"]').clear();
cy.getByTestId('market-pane').should('be.visible'); cy.getByTestId('market-pane').should('be.visible');
cy.get('body').realPress('ArrowDown'); cy.get('body').realPress('ArrowDown');
cy.get('body').realPress('Tab'); cy.get('body').realPress('Tab');
cy.getByTestId('market-pane').should('not.be.visible'); cy.getByTestId('market-pane').should('not.be.visible');
}
}); });
it('mobile view', () => { it('mobile view', () => {
cy.viewport('iphone-xr'); if (markets?.length) {
cy.visit(`/trading/${marketId}`); cy.viewport('iphone-xr');
cy.get('[role="dialog"]').should('not.exist'); cy.visit(`/trading/${markets[0].id}`);
cy.getByTestId('arrow-button').click(); connectVegaWallet();
cy.get('[role="dialog"]').should('be.visible'); cy.get('[role="dialog"]').should('not.exist');
cy.get('input[placeholder="Search"]').then((search) => { cy.getByTestId('arrow-button').click();
cy.wrap(search).clear(); cy.get('[role="dialog"]').should('be.visible');
}); cy.get('input[placeholder="Search"]').clear();
cy.getByTestId('market-pane') cy.getByTestId('market-pane')
.children() .children()
.find('[role="button"]') .find('[role="button"]')
.should('have.length', 3); .should('have.length', 3);
cy.get('div[role="dialog"]').should('have.class', 'w-screen'); cy.get('div[role="dialog"]').should('have.class', 'w-screen');
cy.getByTestId('dialog-close').click(); cy.getByTestId('dialog-close').click();
cy.get('input[placeholder="Search"]').should('have.value', marketName); cy.get('input[placeholder="Search"]').should(
'have.value',
markets[0].tradableInstrument.instrument.name
);
}
}); });
}); });
@@ -1,38 +1,39 @@
import { aliasGQLQuery } from '@vegaprotocol/cypress'; import { aliasQuery } from '@vegaprotocol/cypress';
import { marketQuery, marketsQuery } from '@vegaprotocol/mock'; import { generateSimpleMarkets } from '../support/mocks/generate-markets';
import { generateDealTicket } from '../support/mocks/generate-deal-ticket';
import { generateMarketTags } from '../support/mocks/generate-market-tags';
import { generateMarketPositions } from '../support/mocks/generate-market-positions';
import { generateEstimateOrder } from '../support/mocks/generate-estimate-order';
import { generatePartyBalance } from '../support/mocks/generate-party-balance';
import { generatePartyMarketData } from '../support/mocks/generate-party-market-data';
import { generateMarketMarkPrice } from '../support/mocks/generate-market-mark-price';
import { generateMarketDepth } from '../support/mocks/generate-market-depth';
import { connectVegaWallet } from '../support/connect-wallet';
const marketId = describe('Market trade', () => {
marketsQuery().marketsConnection?.edges[1].node.id || 'market-1'; let markets;
const marketOverride = { beforeEach(() => {
market: { cy.mockGQL((req) => {
depth: { aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets());
lastTrade: { aliasQuery(req, 'DealTicketQuery', generateDealTicket());
price: '9893006', aliasQuery(req, 'MarketTags', generateMarketTags());
}, aliasQuery(req, 'MarketPositions', generateMarketPositions());
}, aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
id: marketId, aliasQuery(req, 'PartyBalanceQuery', generatePartyBalance());
tradableInstrument: { aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
instrument: { aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
product: { aliasQuery(req, 'MarketDepth', generateMarketDepth());
settlementAsset: { });
id: 'asset-id-2', cy.visit('/markets');
name: 'DAI Name', cy.wait('@SimpleMarkets').then((response) => {
symbol: 'tDAI', if (response.response.body.data?.markets?.length) {
}, markets = response.response.body.data.markets;
}, }
}, });
},
},
};
describe('Market trade with wallet disconnected', { tags: '@smoke' }, () => {
before(() => {
cy.mockConsole();
cy.visit(`/trading/${marketId}`);
cy.wait('@Market');
console.log('marketId', marketId);
}); });
it('should not display steps', () => {
it('should not display steps if wallet is disconnected', () => {
cy.visit(`/trading/${markets[0].id}`);
cy.getByTestId('trading-connect-wallet') cy.getByTestId('trading-connect-wallet')
.find('h3') .find('h3')
.should('have.text', 'Please connect your Vega wallet to make a trade'); .should('have.text', 'Please connect your Vega wallet to make a trade');
@@ -43,236 +44,277 @@ describe('Market trade with wallet disconnected', { tags: '@smoke' }, () => {
.find('a') .find('a')
.should('have.text', 'https://vega.xyz/wallet'); .should('have.text', 'https://vega.xyz/wallet');
}); });
});
describe('Market trade', { tags: '@regression' }, () => {
beforeEach(() => {
cy.mockConsole();
cy.mockGQL((req) => {
aliasGQLQuery(req, 'Market', marketQuery(marketOverride));
});
cy.visit(`/trading/${marketId}`);
cy.connectVegaWallet();
cy.wait('@Market');
});
it('side selector should work well', () => { it('side selector should work well', () => {
cy.get('#step-1-control [aria-label^="Selected value"]').should( if (markets?.length) {
'have.text', cy.visit(`/trading/${markets[0].id}`);
'Long' connectVegaWallet();
); cy.get('#step-1-control [aria-label^="Selected value"]').should(
cy.get('#step-1-control [aria-label^="Selected value"]').click(); 'have.text',
cy.get('button[aria-label="Open short position"]').click(); 'Long'
cy.get('#step-2-control').click(); );
cy.get('#step-1-control [aria-label^="Selected value"]').should( cy.get('#step-1-control [aria-label^="Selected value"]').click();
'have.text', cy.get('button[aria-label="Open short position"]').click();
'Short' cy.get('#step-2-control').click();
); cy.get('#step-1-control [aria-label^="Selected value"]').should(
'have.text',
'Short'
);
}
}); });
it('side selector mobile view should work well', () => { it('side selector mobile view should work well', () => {
cy.viewport('iphone-xr'); if (markets?.length) {
cy.getByTestId('next-button').scrollIntoView().click(); cy.viewport('iphone-xr');
cy.visit(`/trading/${markets[0].id}`);
connectVegaWallet();
cy.getByTestId('next-button').scrollIntoView().click();
cy.get('button[aria-label="Open long position"]').should( cy.get('button[aria-label="Open long position"]').should(
'have.class', 'have.class',
'selected' 'selected'
); );
cy.get('button[aria-label="Open short position"]').should( cy.get('button[aria-label="Open short position"]').should(
'not.have.class', 'not.have.class',
'selected' 'selected'
); );
cy.get('button[aria-label="Open short position"]').click(); cy.get('button[aria-label="Open short position"]').click();
cy.get('button[aria-label="Open long position"]').should( cy.get('button[aria-label="Open long position"]').should(
'not.have.class', 'not.have.class',
'selected' 'selected'
); );
cy.get('button[aria-label="Open short position"]').should( cy.get('button[aria-label="Open short position"]').should(
'have.class', 'have.class',
'selected' 'selected'
); );
cy.getByTestId('next-button').scrollIntoView().click(); cy.getByTestId('next-button').scrollIntoView().click();
cy.get('#step-1-control').should( cy.get('#step-1-control').should(
'contain.html', 'contain.html',
'aria-label="Selected value Short"' 'aria-label="Selected value Short"'
); );
}
}); });
it('size slider should work well', () => { it('size slider should work well', () => {
cy.get('#step-1-control [aria-label^="Selected value"]').click(); if (markets?.length) {
cy.get('button[aria-label="Open short position"]').click(); cy.visit(`/trading/${markets[1].id}`);
cy.get('#step-2-control').click(); connectVegaWallet();
cy.get('#step-2-panel') cy.get('#step-1-control [aria-label^="Selected value"]').click();
.find('dd') cy.get('button[aria-label="Open short position"]').click();
.eq(0) cy.get('#step-2-control').click();
.find('button') cy.get('#step-2-panel')
.should('have.text', '1'); .find('dd')
cy.get('#step-2-panel').find('[role="slider"]').type('{rightarrow}'); .eq(0)
.find('button')
.should('have.text', '1');
cy.get('#step-2-panel').find('[role="slider"]').type('{rightarrow}');
cy.get('#step-2-panel') cy.get('#step-2-panel')
.find('dd') .find('dd')
.eq(0) .eq(0)
.find('button') .find('button')
.should('have.text', '2'); .should('have.text', '2');
}
}); });
it('percentage selection should work well', () => { it('percentage selection should work well', () => {
cy.get('#step-1-control [aria-label^="Selected value"]').click(); if (markets?.length) {
cy.get('button[aria-label="Open short position"]').click(); cy.visit(`/trading/${markets[1].id}`);
cy.get('#step-2-control').click(); connectVegaWallet();
cy.get('#step-2-panel') cy.get('#step-1-control [aria-label^="Selected value"]').click();
.find('dd') cy.get('button[aria-label="Open short position"]').click();
.eq(0) cy.get('#step-2-control').click();
.find('button') cy.get('#step-2-panel')
.should('have.text', '1'); .find('dd')
.eq(0)
.find('button')
.should('have.text', '1');
cy.getByTestId('max-label').should('have.text', '10'); cy.getByTestId('max-label').should('have.text', '21');
cy.getByTestId('percentage-selector') cy.getByTestId('percentage-selector')
.find('button') .find('button')
.contains('Max') .contains('Max')
.click(); .click();
cy.get('#step-2-panel') cy.get('#step-2-panel')
.find('dd') .find('dd')
.eq(0) .eq(0)
.find('button') .find('button')
.should('have.text', '10'); .should('have.text', '21');
}
}); });
it('size input should work well', () => { it('size input should work well', () => {
cy.get('#step-1-control [aria-label^="Selected value"]').click(); if (markets?.length) {
cy.get('button[aria-label="Open short position"]').click(); cy.visit(`/trading/${markets[1].id}`);
cy.get('#step-2-control').click(); connectVegaWallet();
cy.get('#step-2-panel') cy.get('#step-1-control [aria-label^="Selected value"]').click();
.find('dd') cy.get('button[aria-label="Open short position"]').click();
.eq(0) cy.get('#step-2-control').click();
.find('button') cy.get('#step-2-panel')
.should('have.text', '1'); .find('dd')
cy.get('#step-2-panel').find('dd').eq(0).find('button').click(); .eq(0)
cy.get('#step-2-panel').find('dd').eq(0).find('input').type('{backspace}2'); .find('button')
cy.get('#step-2-panel').find('dd').eq(0).find('button').click(); .should('have.text', '1');
cy.get('#step-2-panel') cy.get('#step-2-panel').find('dd').eq(0).find('button').click();
.find('dd') cy.get('#step-2-panel')
.eq(0) .find('dd')
.find('button') .eq(0)
.should('have.text', '2'); .find('input')
cy.get('button').contains('Max').click(); .type('{backspace}2');
cy.get('#step-2-panel').find('dd').eq(0).find('button').click();
cy.get('#step-2-panel')
.find('dd')
.eq(0)
.find('button')
.should('have.text', '2');
cy.get('button').contains('Max').click();
}
}); });
it('slippage value should be displayed', () => { it('slippage value should be displayed', () => {
cy.get('#step-1-control [aria-label^="Selected value"]').click(); if (markets?.length) {
cy.get('button[aria-label="Open short position"]').click(); cy.visit(`/trading/${markets[1].id}`);
cy.get('#step-2-control').click(); connectVegaWallet();
cy.get('button').contains('Max').click(); cy.get('#step-1-control [aria-label^="Selected value"]').click();
cy.get('#step-2-panel') cy.get('button[aria-label="Open short position"]').click();
.find('dl') cy.get('#step-2-control').click();
.eq(2) cy.get('button').contains('Max').click();
.find('dd') cy.get('#step-2-panel')
.should('have.text', '0.01%'); .find('dl')
.eq(2)
.find('dd')
.should('have.text', '0.02%');
}
}); });
it('allow slippage value to be adjusted', () => { it('allow slippage value to be adjusted', () => {
cy.get('#step-1-control [aria-label^="Selected value"]').click(); if (markets?.length) {
cy.get('button[aria-label="Open short position"]').click(); cy.visit(`/trading/${markets[1].id}`);
cy.get('#step-2-control').click(); connectVegaWallet();
cy.get('button').contains('Max').click(); cy.get('#step-1-control [aria-label^="Selected value"]').click();
cy.get('#step-2-panel') cy.get('button[aria-label="Open short position"]').click();
.find('dl') cy.get('#step-2-control').click();
.eq(2) cy.get('button').contains('Max').click();
.find('dd') cy.get('#step-2-panel')
.should('have.text', '0.01%'); .find('dl')
cy.get('#step-2-panel').find('dl').eq(2).find('button').click(); .eq(2)
cy.get('#input-order-slippage') .find('dd')
.focus() .should('have.text', '0.02%');
.type('{backspace}{backspace}{backspace}1'); cy.get('#step-2-panel').find('dl').eq(2).find('button').click();
cy.get('#input-order-slippage')
.focus()
.type('{backspace}{backspace}{backspace}1');
cy.getByTestId('slippage-dialog').find('button').click(); cy.getByTestId('slippage-dialog').find('button').click();
cy.get('#step-2-panel') cy.get('#step-2-panel')
.find('dl') .find('dl')
.eq(2) .eq(2)
.find('dd') .find('dd')
.should('have.text', '1%'); .should('have.text', '1%');
}
}); });
it('notional position size should be present', () => { it('notional position size should be present', () => {
cy.get('#step-1-control [aria-label^="Selected value"]').click(); if (markets?.length) {
cy.get('button[aria-label="Open short position"]').click(); cy.visit(`/trading/${markets[1].id}`);
cy.get('#step-2-control').click(); connectVegaWallet();
cy.get('#step-2-panel') cy.get('#step-1-control [aria-label^="Selected value"]').click();
.find('dd') cy.get('button[aria-label="Open short position"]').click();
.eq(0) cy.get('#step-2-control').click();
.find('button') cy.get('#step-2-panel')
.should('have.text', '1'); .find('dd')
cy.get('#step-2-panel').find('dd').eq(0).find('button').click(); .eq(0)
cy.get('#step-2-panel').find('dd').eq(0).find('input').type('{backspace}2'); .find('button')
cy.get('#step-2-panel').find('dd').eq(0).find('button').click(); .should('have.text', '1');
cy.get('#step-2-panel') cy.get('#step-2-panel').find('dd').eq(0).find('button').click();
.find('dt') cy.get('#step-2-panel')
.eq(2) .find('dd')
.should('have.text', 'Est. Position Size (tDAI)'); .eq(0)
cy.get('#step-2-panel').find('dd').eq(2).should('have.text', '197.86012'); .find('input')
.type('{backspace}2');
cy.get('#step-2-panel').find('dd').eq(0).find('button').click();
cy.get('#step-2-panel')
.find('dt')
.eq(2)
.should('have.text', 'Est. Position Size (tDAI)');
cy.get('#step-2-panel').find('dd').eq(2).should('have.text', '197.86012');
}
}); });
it('total fees should be displayed', () => { it('total fees should be displayed', () => {
cy.get('#step-1-control [aria-label^="Selected value"]').click(); if (markets?.length) {
cy.get('button[aria-label="Open short position"]').click(); cy.visit(`/trading/${markets[1].id}`);
cy.get('#step-2-control').click(); connectVegaWallet();
cy.get('#step-2-panel') cy.get('#step-1-control [aria-label^="Selected value"]').click();
.find('dt') cy.get('button[aria-label="Open short position"]').click();
.eq(3) cy.get('#step-2-control').click();
.should('have.text', 'Est. Fees (tDAI)'); cy.get('#step-2-panel')
cy.get('#step-2-panel') .find('dt')
.find('dd') .eq(3)
.eq(3) .should('have.text', 'Est. Fees (tDAI)');
.should('have.text', '3.00 (3.03%)'); cy.get('#step-2-panel')
.find('dd')
.eq(3)
.should('have.text', '3.00000 (3.03%)');
}
}); });
it('order review should display proper calculations', () => { it('order review should display proper calculations', () => {
cy.get('#step-3-control').click(); if (markets?.length) {
cy.visit(`/trading/${markets[0].id}`);
connectVegaWallet();
cy.get('#step-3-control').click();
cy.getByTestId('review-trade') cy.getByTestId('review-trade')
.get('#contracts_tooltip_trigger') .get('#contracts_tooltip_trigger')
.trigger('click') .trigger('click')
.realTouch(); .realTouch();
cy.get('[data-radix-popper-content-wrapper]').contains( cy.get('[data-radix-popper-content-wrapper]').contains(
'The number of contracts determines' 'The number of contracts determines'
); );
cy.get('#step-3-panel').find('dd').eq(1).should('have.text', '1'); cy.get('#step-3-panel').find('dd').eq(1).should('have.text', '1');
cy.get('#step-3-panel').find('dd').eq(2).should('have.text', '98.93006'); cy.get('#step-3-panel').find('dd').eq(2).should('have.text', '98.93006');
cy.get('#step-3-panel') cy.get('#step-3-panel')
.find('dd') .find('dd')
.eq(3) .eq(3)
.should('have.text', '3.00 (3.03%)'); .should('have.text', '3.00000 (3.03%)');
cy.get('#step-3-panel') cy.get('#step-3-panel').find('dd').eq(4).should('have.text', ' - ');
.find('dd')
.eq(4)
.should('have.text', '45,126.90058');
cy.getByTestId('place-order').should('be.enabled').click(); cy.getByTestId('place-order').click();
cy.getByTestId('dialog-title').should(
'have.text',
'Confirm transaction in wallet'
);
}
}); });
it('info tooltip on mobile view should work well', () => { it('info tooltip on mobile view should work well', () => {
cy.viewport('iphone-xr'); if (markets?.length) {
cy.get('#step-3-control').click(); cy.viewport('iphone-xr');
cy.visit(`/trading/${markets[0].id}`);
connectVegaWallet();
cy.get('#step-3-control').click();
// Start from the bottom tooltip to ensure the tooltip above // Start from the bottom tooltip to ensure the tooltip above
// can be interacted with // can be interacted with
cy.getByTestId('review-trade').get('div.cursor-help').eq(1).realTouch(); cy.getByTestId('review-trade').get('div.cursor-help').eq(1).realTouch();
cy.get('[data-radix-popper-content-wrapper]').contains( cy.get('[data-radix-popper-content-wrapper]').contains(
'The notional size represents the position size' 'The notional size represents the position size'
); );
cy.getByTestId('review-trade') cy.getByTestId('review-trade')
.get('#contracts_tooltip_trigger') .get('#contracts_tooltip_trigger')
.realTouch(); .realTouch();
cy.get('[data-radix-popper-content-wrapper]').contains( cy.get('[data-radix-popper-content-wrapper]').contains(
'The number of contracts determines' 'The number of contracts determines'
); );
}
}); });
}); });
@@ -1,194 +1,37 @@
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { import {
accountsQuery, connectVegaWallet,
assetsQuery, disconnectVegaWallet,
chainIdQuery, } from '../support/connect-wallet';
fillsQuery,
marginsQuery, describe('Portfolio page', () => {
marketsDataQuery, afterEach(() => {
marketsQuery, disconnectVegaWallet();
ordersQuery, });
positionsQuery,
statisticsQuery,
} from '@vegaprotocol/mock';
describe('Portfolio page - wallet', { tags: '@smoke' }, () => {
it('button for wallet connect should work', () => { it('button for wallet connect should work', () => {
cy.visit('/'); cy.visit('/');
cy.get('[href="/portfolio"]').eq(0).click(); cy.get('[href="/portfolio"]').eq(0).click();
cy.getByTestId('trading-connect-wallet').should('be.visible'); cy.getByTestId('trading-connect-wallet').should('be.visible');
cy.connectVegaWallet(); connectVegaWallet();
cy.getByTestId('trading-connect-wallet').should('not.exist'); cy.getByTestId('trading-connect-wallet').should('not.exist');
}); });
});
describe('Portfolio page tabs', { tags: '@smoke' }, () => {
before(() => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'Positions', positionsQuery());
aliasGQLQuery(req, 'Margins', marginsQuery());
aliasGQLQuery(req, 'Markets', marketsQuery());
aliasGQLQuery(req, 'MarketsData', marketsDataQuery());
aliasGQLQuery(req, 'Accounts', accountsQuery());
aliasGQLQuery(req, 'Assets', assetsQuery());
});
});
it('certain tabs should exist', () => { it('certain tabs should exist', () => {
cy.visit('/portfolio'); cy.visit('/portfolio');
cy.connectVegaWallet(); connectVegaWallet();
cy.getByTestId('Assets').should('exist');
cy.getByTestId('tab-assets').should('exist');
cy.getByTestId('assets').click(); cy.getByTestId('Positions').click();
cy.location('pathname').should('eq', '/portfolio/assets'); cy.getByTestId('tab-positions').should('exist');
cy.getByTestId('positions').click(); cy.getByTestId('Orders').click();
cy.location('pathname').should('eq', '/portfolio/positions'); cy.getByTestId('tab-orders').should('exist');
cy.getByTestId('orders').click(); cy.getByTestId('Fills').click();
cy.location('pathname').should('eq', '/portfolio/orders'); cy.getByTestId('tab-fills').should('exist');
cy.getByTestId('fills').click(); cy.getByTestId('Deposits').click();
cy.location('pathname').should('eq', '/portfolio/fills'); cy.getByTestId('tab-deposits').should('exist');
cy.getByTestId('deposits').click();
cy.location('pathname').should('eq', '/portfolio/deposits');
});
describe('Assets view', () => {
before(() => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'Positions', positionsQuery());
aliasGQLQuery(req, 'Margins', marginsQuery());
aliasGQLQuery(req, 'Markets', marketsQuery());
aliasGQLQuery(req, 'MarketsData', marketsDataQuery());
aliasGQLQuery(req, 'Accounts', accountsQuery());
aliasGQLQuery(req, 'Assets', assetsQuery());
});
cy.visit('/portfolio/assets');
cy.connectVegaWallet();
});
it('data should be properly rendered', () => {
cy.get('.ag-center-cols-container .ag-row').should('have.length', 5);
cy.get(
'.ag-center-cols-container [row-id="ACCOUNT_TYPE_GENERAL-asset-id-null"]'
)
.find('button')
.click();
cy.getByTestId('dialog-title').should(
'have.text',
'Asset details - tEURO'
);
cy.getByTestId('dialog-close').click();
});
});
describe('Positions view', () => {
beforeEach(() => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'Positions', positionsQuery());
aliasGQLQuery(req, 'Margins', marginsQuery());
aliasGQLQuery(req, 'Markets', marketsQuery());
aliasGQLQuery(req, 'MarketsData', marketsDataQuery());
aliasGQLQuery(req, 'Accounts', accountsQuery());
aliasGQLQuery(req, 'Assets', assetsQuery());
});
cy.visit('/portfolio/positions');
cy.connectVegaWallet();
});
it('data should be properly rendered', () => {
cy.get('.ag-center-cols-container .ag-row').should('have.length', 2);
});
});
describe('Orders view', () => {
beforeEach(() => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'Orders', ordersQuery());
aliasGQLQuery(req, 'Markets', marketsQuery());
});
cy.visit('/portfolio/orders');
cy.connectVegaWallet();
});
it('data should be properly rendered', () => {
cy.get('.ag-center-cols-container .ag-row').should('have.length', 5);
});
});
describe('Fills view', () => {
beforeEach(() => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'Orders', ordersQuery());
aliasGQLQuery(req, 'Markets', marketsQuery());
aliasGQLQuery(req, 'Fills', fillsQuery());
});
cy.visit('/portfolio/fills');
cy.connectVegaWallet();
});
it('data should be properly rendered', () => {
cy.get('.ag-center-cols-container .ag-row').should('have.length', 4);
});
});
describe('Empty views', () => {
beforeEach(() => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'Positions', { party: null });
aliasGQLQuery(req, 'Accounts', { party: null });
aliasGQLQuery(req, 'Orders', { party: null });
aliasGQLQuery(req, 'Fills', { party: null });
aliasGQLQuery(req, 'Markets', {
marketsConnection: { edges: [], __typename: 'MarketConnection' },
});
aliasGQLQuery(req, 'Assets', {
assetsConnection: { edges: null, __typename: 'AssetsConnection' },
});
aliasGQLQuery(req, 'Margins', marginsQuery());
aliasGQLQuery(req, 'MarketsData', marketsDataQuery());
});
cy.visit('/portfolio');
cy.connectVegaWallet();
});
it('"No data to display" should be always displayed', () => {
cy.getByTestId('assets').click();
cy.get('div.flex.items-center.justify-center').should(
'contain.text',
'No data to display'
);
cy.getByTestId('positions').click();
cy.get('div.flex.items-center.justify-center').should(
'contain.text',
'No data to display'
);
cy.getByTestId('orders').click();
cy.get('div.flex.items-center.justify-center').should(
'contain.text',
'No data to display'
);
cy.getByTestId('fills').click();
cy.get('div.flex.items-center.justify-center').should(
'contain.text',
'No data to display'
);
});
}); });
}); });
@@ -0,0 +1,16 @@
export const connectVegaWallet = () => {
const form = 'rest-connector-form';
const walletName = Cypress.env('TRADING_TEST_VEGA_WALLET_NAME');
const walletPassphrase = Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE');
cy.getByTestId('connect-vega-wallet').click();
cy.getByTestId('connectors-list').find('button').click();
cy.getByTestId(form).find('#wallet').click().type(walletName);
cy.getByTestId(form).find('#passphrase').click().type(walletPassphrase);
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
};
export const disconnectVegaWallet = () => {
cy.getByTestId('connect-vega-wallet').click();
cy.getByTestId('disconnect').click();
};
@@ -1,56 +0,0 @@
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import type { CyHttpMessages } from 'cypress/types/net-stubbing';
import {
accountsQuery,
assetsQuery,
chainIdQuery,
estimateOrderQuery,
fillsQuery,
marginsQuery,
marketDataQuery,
marketDepthQuery,
marketQuery,
marketsCandlesQuery,
marketsDataQuery,
marketsQuery,
ordersQuery,
positionsQuery,
statisticsQuery,
} from '@vegaprotocol/mock';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
mockConsole(): void;
}
}
}
const mockPage = (req: CyHttpMessages.IncomingHttpRequest) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'Markets', marketsQuery());
aliasGQLQuery(req, 'MarketsCandles', marketsCandlesQuery());
aliasGQLQuery(req, 'MarketsData', marketsDataQuery());
aliasGQLQuery(req, 'MarketData', marketDataQuery());
aliasGQLQuery(req, 'Market', marketQuery());
aliasGQLQuery(req, 'MarketTags', {});
aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery());
aliasGQLQuery(req, 'MarketNames', {});
aliasGQLQuery(req, 'MarketDepth', marketDepthQuery());
aliasGQLQuery(req, 'Positions', positionsQuery());
aliasGQLQuery(req, 'Margins', marginsQuery());
aliasGQLQuery(req, 'Accounts', accountsQuery());
aliasGQLQuery(req, 'Assets', assetsQuery());
aliasGQLQuery(req, 'SimpleMarkets', marketsQuery());
aliasGQLQuery(req, 'Orders', ordersQuery());
aliasGQLQuery(req, 'Fills', fillsQuery());
};
export const addMockConsole = () => {
Cypress.Commands.add('mockConsole', () => {
cy.mockGQL(mockPage);
});
};
@@ -1,7 +0,0 @@
import '@vegaprotocol/cypress';
import 'cypress-real-events/support';
import registerCypressGrep from '@cypress/grep';
import { addMockConsole } from './console-mock';
registerCypressGrep();
addMockConsole();
@@ -0,0 +1,105 @@
const protoCandles = [
{ open: '9556163', close: '9587028', __typename: 'Candle' },
{
open: '9587028',
close: '9769899',
__typename: 'Candle',
},
{ open: '9769899', close: '9586292', __typename: 'Candle' },
{
open: '9586292',
close: '9261774',
__typename: 'Candle',
},
{ open: '9261773', close: '9236369', __typename: 'Candle' },
{
open: '9236369',
close: '9226070',
__typename: 'Candle',
},
{ open: '9226077', close: '9233252', __typename: 'Candle' },
{
open: '9249854',
close: '9333038',
__typename: 'Candle',
},
{ open: '9333038', close: '9410371', __typename: 'Candle' },
{
open: '9410371',
close: '9626249',
__typename: 'Candle',
},
{ open: '9626247', close: '9493253', __typename: 'Candle' },
{
open: '9493253',
close: '9309054',
__typename: 'Candle',
},
{ open: '9309054', close: '9378428', __typename: 'Candle' },
{
open: '9378428',
close: '9352996',
__typename: 'Candle',
},
{ open: '9352996', close: '9451142', __typename: 'Candle' },
{
open: '9451142',
close: '9691070',
__typename: 'Candle',
},
{ open: '9691071', close: '9622031', __typename: 'Candle' },
{
open: '9622034',
close: '9519285',
__typename: 'Candle',
},
{ open: '9528904', close: '9671275', __typename: 'Candle' },
{
open: '9671275',
close: '9988454',
__typename: 'Candle',
},
{ open: '9982457', close: '10085537', __typename: 'Candle' },
{
open: '10085537',
close: '9967390',
__typename: 'Candle',
},
{ open: '9967390', close: '9974844', __typename: 'Candle' },
{
open: '9974844',
close: '9940706',
__typename: 'Candle',
},
];
export const protoMarket = {
id: 'first-btcusd-id',
state: 'STATE_ACTIVE',
tradableInstrument: {
instrument: {
code: 'AAVEDAI.MF21',
name: 'AAVEDAI Monthly (30 Jun 2022)',
metadata: {
tags: [
'formerly:2839D9B2329C9E70',
'base:AAVE',
'quote:DAI',
'class:fx/crypto',
'monthly',
'sector:defi',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'DAI',
settlementAsset: { symbol: 'tDAI', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: protoCandles,
__typename: 'Market',
};
@@ -0,0 +1,33 @@
export const generateDealTicket = () => {
return {
market: {
id: 'first-btcusd-id',
decimalPlaces: 5,
positionDecimalPlaces: 0,
state: 'STATE_ACTIVE',
tradingMode: 'Continuous',
tradableInstrument: {
instrument: {
name: 'AAVEDAI Monthly (30 Jun 2022)',
product: {
quoteName: 'DAI',
settlementAsset: {
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI',
name: 'tDAI TEST',
__typename: 'Asset',
},
__typename: 'Future',
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
depth: {
lastTrade: { price: '9893006', __typename: 'Trade' },
__typename: 'MarketDepth',
},
__typename: 'Market',
},
};
};
@@ -0,0 +1,17 @@
export const generateEstimateOrder = () => {
return {
estimateOrder: {
fee: {
__typename: 'TradeFee',
makerFee: '100000',
liquidityFee: '100000',
infrastructureFee: '100000',
},
marginLevels: {
initialLevel: '2844054.80937741220203',
__typename: 'MarginLevels',
},
__typename: 'OrderEstimate',
},
};
};
@@ -0,0 +1,218 @@
export const generateMarketDepth = () => {
return {
market: {
id: 'a46bd7e5277087723b7ab835844dec3cef8b4445738101269624bf5537d5d423',
decimalPlaces: 5,
positionDecimalPlaces: 0,
data: {
staticMidPrice: '9893006',
marketTradingMode: 'TRADING_MODE_CONTINUOUS',
indicativeVolume: '0',
indicativePrice: '0',
bestStaticBidPrice: '9893006',
bestStaticOfferPrice: '9893006',
market: {
id: 'a46bd7e5277087723b7ab835844dec3cef8b4445738101269624bf5537d5d423',
__typename: 'Market',
},
__typename: 'MarketData',
},
depth: {
lastTrade: { price: '9893006', __typename: 'Trade' },
sell: [
{
price: '9893007',
volume: '3',
numberOfOrders: '3',
__typename: 'PriceLevel',
},
{
price: '9893010',
volume: '4',
numberOfOrders: '4',
__typename: 'PriceLevel',
},
{
price: '9893012',
volume: '1',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9893015',
volume: '1',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9893017',
volume: '2',
numberOfOrders: '2',
__typename: 'PriceLevel',
},
{
price: '9893021',
volume: '4',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9893025',
volume: '5',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9893125',
volume: '4',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9893135',
volume: '2',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9893165',
volume: '5',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9893175',
volume: '3',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9893185',
volume: '3',
numberOfOrders: '3',
__typename: 'PriceLevel',
},
{
price: '9894185',
volume: '1',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9894585',
volume: '1',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9895585',
volume: '4',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9896585',
volume: '2',
numberOfOrders: '2',
__typename: 'PriceLevel',
},
],
buy: [
{
price: '9893005',
volume: '4',
numberOfOrders: '3',
__typename: 'PriceLevel',
},
{
price: '9893003',
volume: '2',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9893001',
volume: '1',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9892006',
volume: '3',
numberOfOrders: '2',
__typename: 'PriceLevel',
},
{
price: '9891006',
volume: '2',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9891001',
volume: '1',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9890101',
volume: '2',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9890091',
volume: '5',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9890081',
volume: '4',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9890050',
volume: '2',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9890040',
volume: '6',
numberOfOrders: '3',
__typename: 'PriceLevel',
},
{
price: '9890030',
volume: '6',
numberOfOrders: '2',
__typename: 'PriceLevel',
},
{
price: '9890021',
volume: '3',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9890011',
volume: '1',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
{
price: '9890001',
volume: '11',
numberOfOrders: '1',
__typename: 'PriceLevel',
},
],
sequenceNumber: '1661773865550746910',
__typename: 'MarketDepth',
},
__typename: 'Market',
},
};
};
@@ -0,0 +1,9 @@
export const generateMarketMarkPrice = () => {
return {
market: {
decimalPlaces: 5,
data: { markPrice: '692748', __typename: 'MarketData' },
__typename: 'Market',
},
};
};
@@ -0,0 +1,136 @@
import { protoMarket } from './commons';
export const generateMarketNames = () => {
return {
markets: [
{ ...protoMarket },
{
id: '1d7ddf67dac4924db03f5bf58571a7bcb1908d70c66580467717aabc5345b68a',
state: 'STATE_SUSPENDED',
tradableInstrument: {
instrument: {
code: 'AAPL.MF21',
name: 'Apple Monthly (30 Jun 2022)',
metadata: {
tags: [
'formerly:4899E01009F1A721',
'quote:USD',
'ticker:AAPL',
'class:equities/single-stock-futures',
'sector:tech',
'listing_venue:NASDAQ',
'country:US',
],
__typename: 'InstrumentMetadata',
},
product: { quoteName: 'USD', __typename: 'Future' },
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
{
id: '87ae87cd3244fc1fab4b0e2dad2437879864192bb969f3109b69293421644c8b',
state: 'STATE_SUSPENDED',
tradableInstrument: {
instrument: {
code: 'TSLA.QM21',
name: 'Tesla Quarterly (30 Jun 2022)',
metadata: {
tags: [
'formerly:5A86B190C384997F',
'quote:EURO',
'ticker:TSLA',
'class:equities/single-stock-futures',
'sector:tech',
'listing_venue:NASDAQ',
'country:US',
],
__typename: 'InstrumentMetadata',
},
product: { quoteName: 'EURO', __typename: 'Future' },
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
{
id: '69205712a854f1bbfb69fa3d11b60e01a1e249bafb5ece88115e7451e8ef07b3',
state: 'STATE_SUSPENDED',
tradableInstrument: {
instrument: {
code: 'BTCUSD.MF21',
name: 'BTCUSD Monthly (30 Jun 2022)',
metadata: {
tags: [
'formerly:076BB86A5AA41E3E',
'base:BTC',
'quote:USD',
'class:fx/crypto',
'monthly',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: { quoteName: 'USD', __typename: 'Future' },
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
{
id: 'ethbtc-quaterly',
state: 'STATE_ACTIVE',
tradableInstrument: {
instrument: {
code: 'ETHBTC.QM21',
name: 'ETHBTC Quarterly (30 Jun 2022)',
metadata: {
tags: [
'formerly:1F0BB6EB5703B099',
'base:ETH',
'quote:BTC',
'class:fx/crypto',
'quarterly',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: { quoteName: 'BTC', __typename: 'Future' },
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
{
id: '3c62b2714c4332d1a689a5352bff090e6aabccfd6bd87ce018936b38372530c9',
state: 'STATE_ACTIVE',
tradableInstrument: {
instrument: {
code: 'UNIDAI.MF21',
name: 'UNIDAI Monthly (30 Jun 2022)',
metadata: {
tags: [
'formerly:3C58ED2A4A6C5D7E',
'base:UNI',
'quote:DAI',
'class:fx/crypto',
'monthly',
'sector:defi',
],
__typename: 'InstrumentMetadata',
},
product: { quoteName: 'DAI', __typename: 'Future' },
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
],
};
};
@@ -0,0 +1,59 @@
export const generateMarketPositions = () => {
return {
party: {
id: '2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
accounts: [
{
type: 'General',
asset: {
decimals: 5,
},
balance: '400000000000000000000',
market: {
id: '2751c508f9759761f912890f37fb3f97a00300bf7685c02a56a86e05facfe221',
__typename: 'Market',
},
},
{
type: 'Margin',
asset: {
decimals: 5,
},
balance: '265329',
market: {
id: 'first-btcusd-id',
__typename: 'Market',
},
},
],
positionsConnection: {
edges: [
{
node: {
openVolume: '3',
market: {
id: '2751c508f9759761f912890f37fb3f97a00300bf7685c02a56a86e05facfe221',
__typename: 'Market',
},
__typename: 'Position',
},
__typename: 'PositionEdge',
},
{
node: {
openVolume: '12',
market: {
id: 'first-btcusd-id',
__typename: 'Market',
},
__typename: 'Position',
},
__typename: 'PositionEdge',
},
],
__typename: 'PositionConnection',
},
__typename: 'Party',
},
};
};
@@ -0,0 +1,25 @@
export const generateMarketTags = () => {
return {
market: {
tradableInstrument: {
instrument: {
metadata: {
tags: [
'formerly:2839D9B2329C9E70',
'base:AAVE',
'quote:DAI',
'class:fx/crypto',
'monthly',
'sector:defi',
'settlement:2022-08-01',
],
__typename: 'InstrumentMetadata',
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
};
};
@@ -0,0 +1,882 @@
import { protoMarket } from './commons';
export const generateSimpleMarkets = () => {
return {
markets: [
{ ...protoMarket },
{
id: '57fbaa322e97cfc8bb5f1de048c37e033c41b1ac1906d3aed9960912a067ef5a',
state: 'Active',
tradableInstrument: {
instrument: {
code: 'CELUSD',
name: 'CELUSD (June 2022)',
metadata: {
tags: [
'base:CEL',
'quote:USD',
'class:fx/crypto',
'ad-hoc',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'XYZalpha', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: null,
__typename: 'Market',
},
{
id: 'ccf2f04865e5951ac3405da6e16b7cbdb535a0ad32df4df2dbed4262cf473255',
state: 'Suspended',
tradableInstrument: {
instrument: {
code: 'XMRUSD',
name: 'XMRUSD market',
metadata: {
tags: [
'base:monero',
'quote:usd',
'class:fx/crypto',
'ad-hoc',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'XYZbeta', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: null,
__typename: 'Market',
},
{
id: '6030e5b4e0ca3297a26081e5af4d453f97f96baab2d74bf56f84efcffc4c382f',
state: 'Active',
tradableInstrument: {
instrument: {
code: 'UNIDAI.MF21',
name: 'UNIDAI Monthly (30 Jun 2022)',
metadata: {
tags: [
'formerly:3C58ED2A4A6C5D7E',
'base:UNI',
'quote:DAI',
'class:fx/crypto',
'monthly',
'sector:defi',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'DAI',
settlementAsset: { symbol: 'tDAI', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: [
{ open: '723082', close: '726800', __typename: 'Candle' },
{
open: '726800',
close: '733551',
__typename: 'Candle',
},
{ open: '733557', close: '719960', __typename: 'Candle' },
{
open: '719961',
close: '707711',
__typename: 'Candle',
},
{ open: '702459', close: '705275', __typename: 'Candle' },
{
open: '701609',
close: '698494',
__typename: 'Candle',
},
{ open: '698494', close: '702201', __typename: 'Candle' },
{
open: '704749',
close: '704248',
__typename: 'Candle',
},
{ open: '708562', close: '714712', __typename: 'Candle' },
{
open: '718434',
close: '732567',
__typename: 'Candle',
},
{ open: '732567', close: '750475', __typename: 'Candle' },
{
open: '768916',
close: '746371',
__typename: 'Candle',
},
{ open: '740240', close: '744910', __typename: 'Candle' },
{
open: '746902',
close: '731680',
__typename: 'Candle',
},
{ open: '730156', close: '729021', __typename: 'Candle' },
{
open: '730503',
close: '738096',
__typename: 'Candle',
},
{ open: '738096', close: '743087', __typename: 'Candle' },
{
open: '739244',
close: '732352',
__typename: 'Candle',
},
{ open: '732352', close: '751259', __typename: 'Candle' },
{
open: '751259',
close: '751455',
__typename: 'Candle',
},
{ open: '751455', close: '753990', __typename: 'Candle' },
{
open: '757174',
close: '761100',
__typename: 'Candle',
},
{ open: '758974', close: '759379', __typename: 'Candle' },
{
open: '762834',
close: '761777',
__typename: 'Candle',
},
],
__typename: 'Market',
},
{
id: 'de74a5572045b300e8ec50d136896912ec76e7d7ec135bc305dfd4854d9363a4',
state: 'Pending',
tradableInstrument: {
instrument: {
code: 'XMRUSD',
name: 'XMRUSD market',
metadata: {
tags: [
'base:monero',
'quote:usd',
'class:fx/crypto',
'ad-hoc',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'XYZbeta', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: [
{ open: '14602', close: '14596', __typename: 'Candle' },
{
open: '14596',
close: '14731',
__typename: 'Candle',
},
{ open: '14732', close: '14592', __typename: 'Candle' },
{
open: '14592',
close: '14516',
__typename: 'Candle',
},
{ open: '14516', close: '14179', __typename: 'Candle' },
{
open: '14179',
close: '14031',
__typename: 'Candle',
},
{ open: '14032', close: '14115', __typename: 'Candle' },
{
open: '14116',
close: '14166',
__typename: 'Candle',
},
{ open: '14166', close: '14377', __typename: 'Candle' },
{
open: '14377',
close: '14714',
__typename: 'Candle',
},
{ open: '14713', close: '14593', __typename: 'Candle' },
{
open: '14594',
close: '14273',
__typename: 'Candle',
},
{ open: '14272', close: '14245', __typename: 'Candle' },
{
open: '14244',
close: '14337',
__typename: 'Candle',
},
{ open: '14338', close: '14384', __typename: 'Candle' },
{
open: '14385',
close: '14257',
__typename: 'Candle',
},
{ open: '14256', close: '14105', __typename: 'Candle' },
{
open: '14106',
close: '14067',
__typename: 'Candle',
},
{ open: '14066', close: '14196', __typename: 'Candle' },
{
open: '14197',
close: '14316',
__typename: 'Candle',
},
{ open: '14315', close: '14377', __typename: 'Candle' },
{
open: '14378',
close: '14106',
__typename: 'Candle',
},
{ open: '14105', close: '14317', __typename: 'Candle' },
{
open: '14318',
close: '14467',
__typename: 'Candle',
},
],
__typename: 'Market',
},
{
id: '31ea96284611771e486c820acb26a325a99664f9854b5a7e7ad99023efa8f9e6',
state: 'Closed',
tradableInstrument: {
instrument: {
code: 'APE/USD',
name: 'ApeCoin (18 Jul 2022)',
metadata: {
tags: [
'quote:USD',
'ticker:APE',
'class:equities/single-stock-futures',
'sector:tech',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'tUSDC', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: null,
__typename: 'Market',
},
{
id: '34cff959cdc2ffdb0f167820d701fe8b51cc6b8588e650d93369aaa22d6f8b74',
state: 'Settled',
tradableInstrument: {
instrument: {
code: 'BTCUSD.MF21',
name: 'BTCUSD Monthly (18 Jul 2022)',
metadata: {
tags: [
'formerly:076BB86A5AA41E3E',
'base:BTC',
'quote:USD',
'class:fx/crypto',
'monthly',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'tBTC', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: null,
__typename: 'Market',
},
{
id: '87b0bbb3c171baa5d97dfc3852332829c91e5c5dc9f7c8fb584c6d8ac75aaaf2',
state: 'Settled',
tradableInstrument: {
instrument: {
code: 'LTCUSD',
name: 'Builders Club x ETHcc',
metadata: {
tags: [
'base:litecoin',
'quote:usd',
'class:fx/crypto',
'ad-hoc',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'XYZgamma', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: [
{ open: '5700', close: '5688', __typename: 'Candle' },
{
open: '5688',
close: '5644',
__typename: 'Candle',
},
{ open: '5644', close: '5664', __typename: 'Candle' },
{
open: '5664',
close: '5694',
__typename: 'Candle',
},
{ open: '5694', close: '5751', __typename: 'Candle' },
{
open: '5751',
close: '5853',
__typename: 'Candle',
},
{ open: '5853', close: '5773', __typename: 'Candle' },
{
open: '5773',
close: '5697',
__typename: 'Candle',
},
{ open: '5697', close: '5683', __typename: 'Candle' },
{
open: '5683',
close: '5655',
__typename: 'Candle',
},
{ open: '5655', close: '5674', __typename: 'Candle' },
{
open: '5674',
close: '5705',
__typename: 'Candle',
},
{ open: '5705', close: '5648', __typename: 'Candle' },
{
open: '5648',
close: '5646',
__typename: 'Candle',
},
{ open: '5646', close: '5687', __typename: 'Candle' },
{
open: '5687',
close: '5675',
__typename: 'Candle',
},
{ open: '5675', close: '5696', __typename: 'Candle' },
{
open: '5696',
close: '5699',
__typename: 'Candle',
},
{ open: '5699', close: '5731', __typename: 'Candle' },
{
open: '5731',
close: '5768',
__typename: 'Candle',
},
],
__typename: 'Market',
},
{
id: '45266143c6e9b58f4cff9b8906e971c531bb29ea7af01066973f9b77e8134823',
state: 'Pending',
tradableInstrument: {
instrument: {
code: 'BTCUSD.MF21',
name: 'BTCUSD Monthly (18 Jul 2022)',
metadata: {
tags: [
'formerly:076BB86A5AA41E3E',
'base:BTC',
'quote:USD',
'class:fx/crypto',
'monthly',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'tBTC', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: null,
__typename: 'Market',
},
{
id: '65663ebdc96161162769c4d5c5508137416748178d7cb28e2cb0d07a151a2bc6',
state: 'Active',
tradableInstrument: {
instrument: {
code: 'ETHBTC.QM21',
name: 'ETHBTC Quarterly (30 Jun 2022)',
metadata: {
tags: [
'formerly:1F0BB6EB5703B099',
'base:ETH',
'quote:BTC',
'class:fx/crypto',
'quarterly',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'BTC',
settlementAsset: { symbol: 'tBTC', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: [
{ open: '6680', close: '6600', __typename: 'Candle' },
{
open: '6600',
close: '6630',
__typename: 'Candle',
},
{ open: '6630', close: '6677', __typename: 'Candle' },
{
open: '6735',
close: '6720',
__typename: 'Candle',
},
{ open: '6720', close: '6691', __typename: 'Candle' },
{
open: '6681',
close: '6730',
__typename: 'Candle',
},
{ open: '6756', close: '6778', __typename: 'Candle' },
{
open: '6778',
close: '6768',
__typename: 'Candle',
},
{ open: '6768', close: '6834', __typename: 'Candle' },
{
open: '6834',
close: '7001',
__typename: 'Candle',
},
{ open: '7001', close: '6894', __typename: 'Candle' },
{
open: '6910',
close: '6867',
__typename: 'Candle',
},
{ open: '6867', close: '6827', __typename: 'Candle' },
{
open: '6899',
close: '6910',
__typename: 'Candle',
},
{ open: '6868', close: '6902', __typename: 'Candle' },
{
open: '6905',
close: '6947',
__typename: 'Candle',
},
{ open: '6932', close: '6900', __typename: 'Candle' },
{
open: '6900',
close: '6911',
__typename: 'Candle',
},
{ open: '6936', close: '6948', __typename: 'Candle' },
{
open: '6936',
close: '6946',
__typename: 'Candle',
},
{ open: '6946', close: '6906', __typename: 'Candle' },
{
open: '6947',
close: '6962',
__typename: 'Candle',
},
{ open: '6962', close: '7027', __typename: 'Candle' },
{
open: '6941',
close: '6921',
__typename: 'Candle',
},
],
__typename: 'Market',
},
{
id: '234afabd27e3bce1a879039c041f9f00f915d98459935ddafd0169d38dd13850',
state: 'Active',
tradableInstrument: {
instrument: {
code: 'AAPL.MF21',
name: 'Apple Monthly (30 Jun 2022)',
metadata: {
tags: [
'formerly:4899E01009F1A721',
'quote:USD',
'ticker:AAPL',
'class:equities/single-stock-futures',
'sector:tech',
'listing_venue:NASDAQ',
'country:US',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'tUSDC', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: [
{ open: '15126319', close: '15087482', __typename: 'Candle' },
{
open: '15087482',
close: '15119759',
__typename: 'Candle',
},
{ open: '15119759', close: '15061361', __typename: 'Candle' },
{
open: '15061361',
close: '14742622',
__typename: 'Candle',
},
{ open: '14742622', close: '14735571', __typename: 'Candle' },
{
open: '14735571',
close: '14694437',
__typename: 'Candle',
},
{ open: '14694437', close: '14670479', __typename: 'Candle' },
{
open: '14670479',
close: '14624796',
__typename: 'Candle',
},
{ open: '14621001', close: '14722835', __typename: 'Candle' },
{
open: '14722835',
close: '14705183',
__typename: 'Candle',
},
{ open: '14705183', close: '14710797', __typename: 'Candle' },
{
open: '14710796',
close: '14713874',
__typename: 'Candle',
},
{ open: '14713874', close: '14677982', __typename: 'Candle' },
{
open: '14677982',
close: '14710031',
__typename: 'Candle',
},
{ open: '14751685', close: '14737556', __typename: 'Candle' },
{
open: '14737556',
close: '14769139',
__typename: 'Candle',
},
{ open: '14760616', close: '14761691', __typename: 'Candle' },
{
open: '14761691',
close: '14714380',
__typename: 'Candle',
},
{ open: '14714380', close: '14740160', __typename: 'Candle' },
{
open: '14740160',
close: '14764832',
__typename: 'Candle',
},
{ open: '14764832', close: '14778059', __typename: 'Candle' },
{
open: '14761936',
close: '14707831',
__typename: 'Candle',
},
{ open: '14707831', close: '14753272', __typename: 'Candle' },
{
open: '14753272',
close: '14771411',
__typename: 'Candle',
},
],
__typename: 'Market',
},
{
id: '9f2a3c1caa67bb0773ec18d908d32b55b129b9ec2d106a8e9a87f6aa5c0375a6',
state: 'Active',
tradableInstrument: {
instrument: {
code: 'TSLA.QM21',
name: 'Tesla Quarterly (30 Jun 2022)',
metadata: {
tags: [
'formerly:5A86B190C384997F',
'quote:EURO',
'ticker:TSLA',
'class:equities/single-stock-futures',
'sector:tech',
'listing_venue:NASDAQ',
'country:US',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'EURO',
settlementAsset: { symbol: 'tEURO', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: [
{ open: '71263667', close: '71173749', __typename: 'Candle' },
{
open: '71173749',
close: '71055959',
__typename: 'Candle',
},
{ open: '71055959', close: '71078605', __typename: 'Candle' },
{
open: '71078605',
close: '69838205',
__typename: 'Candle',
},
{ open: '69838205', close: '69166023', __typename: 'Candle' },
{
open: '69166023',
close: '68841759',
__typename: 'Candle',
},
{ open: '68841759', close: '68711290', __typename: 'Candle' },
{
open: '68711290',
close: '68650154',
__typename: 'Candle',
},
{ open: '68644289', close: '68638322', __typename: 'Candle' },
{
open: '68704232',
close: '68343581',
__typename: 'Candle',
},
{ open: '67345721', close: '67554701', __typename: 'Candle' },
{
open: '67533143',
close: '67551571',
__typename: 'Candle',
},
{ open: '67533286', close: '67543539', __typename: 'Candle' },
{
open: '67580208',
close: '67472337',
__typename: 'Candle',
},
{ open: '67416289', close: '67459697', __typename: 'Candle' },
{
open: '67396085',
close: '67528265',
__typename: 'Candle',
},
{ open: '67491799', close: '67538833', __typename: 'Candle' },
{
open: '67493210',
close: '67362908',
__typename: 'Candle',
},
{ open: '67362908', close: '67518182', __typename: 'Candle' },
{
open: '67518182',
close: '67635775',
__typename: 'Candle',
},
{ open: '67635775', close: '67734861', __typename: 'Candle' },
{
open: '67714943',
close: '67736742',
__typename: 'Candle',
},
{ open: '67716074', close: '67690826', __typename: 'Candle' },
{
open: '67714352',
close: '67817444',
__typename: 'Candle',
},
],
__typename: 'Market',
},
{
id: '8a45ee934d3ddac4b036f9884df1064a5353c620a56f775ba36597d0edef9a7a',
state: 'Active',
tradableInstrument: {
instrument: {
code: 'LTCUSD',
name: 'Go big or Go home',
metadata: {
tags: [
'base:litecoin',
'quote:usd',
'class:fx/crypto',
'ad-hoc',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'XYZgamma', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: [
{ open: '5809', close: '5795', __typename: 'Candle' },
{
open: '5795',
close: '5845',
__typename: 'Candle',
},
{ open: '5845', close: '5779', __typename: 'Candle' },
{
open: '5779',
close: '5710',
__typename: 'Candle',
},
{ open: '5710', close: '5691', __typename: 'Candle' },
],
__typename: 'Market',
},
{
id: 'aede7b9ac0c3b225004929c5455160a00f59864aad32ec366e8a2bff1b30fd0f',
state: 'Active',
tradableInstrument: {
instrument: {
code: 'BTCUSD.MF21',
name: 'BTCUSD Monthly (08 Jul 2022)',
metadata: {
tags: [
'formerly:076BB86A5AA41E3E',
'base:BTC',
'quote:USD',
'class:fx/crypto',
'monthly',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'tBTC', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: null,
__typename: 'Market',
},
{
id: '9398707e01daa1a1f1ca6ff87cf8d6c03fe7373ce31121ce81b99a129e6bda47',
state: 'Active',
tradableInstrument: {
instrument: {
code: 'BTCUSD.MF21',
name: 'BTCUSD Monthly (18 Jul 2022)',
metadata: {
tags: [
'formerly:076BB86A5AA41E3E',
'base:BTC',
'quote:USD',
'class:fx/crypto',
'monthly',
'sector:crypto',
],
__typename: 'InstrumentMetadata',
},
product: {
__typename: 'Future',
quoteName: 'USD',
settlementAsset: { symbol: 'tBTC', __typename: 'Asset' },
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles: null,
__typename: 'Market',
},
],
};
};
export const generateLongListMarkets = (count: number) => {
const markets = [];
for (let i = 0; i < count; i++) {
markets.push({
...protoMarket,
id: protoMarket.id + i,
tradableInstrument: {
...protoMarket.tradableInstrument,
instrument: {
...protoMarket.tradableInstrument.instrument,
name: protoMarket.tradableInstrument.instrument.name + i,
},
},
});
}
return { markets };
};
@@ -0,0 +1,57 @@
export const generatePartyBalance = () => {
return {
party: {
accounts: [
{
balance: '88474051',
type: 'ACCOUNT_TYPE_GENERAL',
asset: {
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI',
name: 'tDAI TEST',
decimals: 5,
__typename: 'Asset',
},
__typename: 'Account',
},
{
balance: '100000000',
type: 'ACCOUNT_TYPE_GENERAL',
asset: {
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
symbol: 'tEURO',
name: 'tEURO TEST',
decimals: 5,
__typename: 'Asset',
},
__typename: 'Account',
},
{
balance: '3412867',
type: 'ACCOUNT_TYPE_GENERAL',
asset: {
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI',
name: 'tDAI TEST',
decimals: 5,
__typename: 'Asset',
},
__typename: 'Account',
},
{
balance: '70007',
type: 'ACCOUNT_TYPE_GENERAL',
asset: {
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI',
name: 'tDAI TEST',
decimals: 5,
__typename: 'Asset',
},
__typename: 'Account',
},
],
__typename: 'Party',
},
};
};
@@ -0,0 +1,19 @@
export const generatePartyMarketData = () => {
return {
party: {
id: '2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
accounts: [
{
type: 'General',
balance: '1200000',
asset: { id: 'fBTC', decimals: 5, __typename: 'Asset' },
market: null,
__typename: 'Account',
},
],
marginsConnection: { edges: null, __typename: 'MarginConnection' },
positionsConnection: { edges: null, __typename: 'PositionConnection' },
__typename: 'Party',
},
};
};
+2 -3
View File
@@ -1,13 +1,12 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"strict": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"sourceMap": false, "sourceMap": false,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"outDir": "../../dist/out-tsc", "outDir": "../../dist/out-tsc",
"allowJs": true, "allowJs": true,
"types": ["cypress", "node", "cypress-real-events", "@cypress/grep"] "types": ["cypress", "node", "cypress-real-events"]
}, },
"include": ["src/**/*.ts", "src/**/*.js", "./declaration.d.ts", "index.d.ts"] "include": ["src/**/*.ts", "src/**/*.js"]
} }
+6 -6
View File
@@ -17,11 +17,11 @@ NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
NX_URL=$URL NX_URL=$URL
NX_DEPLOY_URL=$DEPLOY_URL NX_DEPLOY_URL=$DEPLOY_URL
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
NX_VEGA_CONFIG_URL="https://static.vega.xyz/assets/stagnet3-network.json" NX_VEGA_CONFIG_URL="https://static.vega.xyz/assets/testnet-network.json"
NX_VEGA_ENV=STAGNET3 NX_VEGA_ENV = 'TESTNET'
NX_VEGA_URL="https://api.stagnet3.vega.xyz/graphql" NX_VEGA_URL="https://api.n11.testnet.vega.xyz/graphql"
NX_VEGA_WALLET_URL=http://localhost:1789 NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_NETWORKS={"MAINNET":"https://alpha.console.vega.xyz"} NX_VEGA_NETWORKS={"MAINNET":"https://alpha.console.vega.xyz"}
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
+1
View File
@@ -1,3 +1,4 @@
# App configuration variables # App configuration variables
NX_VEGA_URL=http://localhost:3028/query NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_ENV=LOCAL NX_VEGA_ENV=LOCAL
NX_VEGA_REST=http://localhost:3029
+3 -2
View File
@@ -2,7 +2,8 @@
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
NX_VEGA_ENV=DEVNET NX_VEGA_ENV=DEVNET
NX_VEGA_REST=https://api.n04.d.vega.xyz
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"} NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"}
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
+2 -1
View File
@@ -1,7 +1,8 @@
# App configuration variables # App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_URL=https://api.vega.xyz/query NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_VEGA_REST=https://api.token.vega.xyz
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}' NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
-7
View File
@@ -1,7 +0,0 @@
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/sandbox-network.json
NX_VEGA_URL=https://api.sandbox.vega.xyz/graphql
NX_VEGA_ENV=SANDBOX
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/sandbox-network.json
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
-10
View File
@@ -1,10 +0,0 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"STAGNET3\":\"https://stagnet3.console.vega.xyz\"}
NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
NX_VEGA_URL=https://api.n00.stagnet1.vega.xyz/graphql
NX_VEGA_WALLET_URL=http://localhost:1789
+5 -4
View File
@@ -1,7 +1,8 @@
# App configuration variables # App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql NX_VEGA_URL=https://api.n01.stagnet3.vega.xyz/graphql
NX_VEGA_ENV=STAGNET3 NX_VEGA_ENV=STAGNET3
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_VEGA_REST=https://api.n01.stagnet3.vega.xyz
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_EXPLORER_URL=https://staging2.explorer.vega.xyz
+4 -3
View File
@@ -1,8 +1,9 @@
# App configuration variables # App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_URL=https://api.n12.testnet.vega.xyz/graphql NX_VEGA_URL=https://api.n11.testnet.vega.xyz/graphql
NX_VEGA_ENV=TESTNET NX_VEGA_ENV=TESTNET
NX_VEGA_REST=https://api.n11.testnet.vega.xyz
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}' NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
-7
View File
@@ -77,13 +77,6 @@
"nx build console-lite" "nx build console-lite"
] ]
} }
},
"build-spec": {
"executor": "@nrwl/workspace:run-commands",
"outputs": [],
"options": {
"command": "yarn tsc --project ./apps/console-lite/tsconfig.spec.json"
}
} }
}, },
"tags": [] "tags": []
-3
View File
@@ -1,5 +1,4 @@
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import 'jest-canvas-mock';
import { defaultFallbackInView } from 'react-intersection-observer'; import { defaultFallbackInView } from 'react-intersection-observer';
import ResizeObserver from 'resize-observer-polyfill'; import ResizeObserver from 'resize-observer-polyfill';
@@ -39,5 +38,3 @@ Object.defineProperty(window, 'matchMedia', {
dispatchEvent: jest.fn(), dispatchEvent: jest.fn(),
})), })),
}); });
jest.setTimeout(30000);
+28 -43
View File
@@ -1,5 +1,8 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { createClient } from './lib/apollo-client';
import { ThemeContext } from '@vegaprotocol/react-helpers';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment'; import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
import { import {
VegaConnectDialog, VegaConnectDialog,
@@ -13,10 +16,10 @@ import Header from './components/header';
import { Main } from './components/main'; import { Main } from './components/main';
import LocalContext from './context/local-context'; import LocalContext from './context/local-context';
import useLocalValues from './hooks/use-local-values'; import useLocalValues from './hooks/use-local-values';
import type { InMemoryCacheConfig } from '@apollo/client';
function App() { function App() {
const localValues = useLocalValues(); const [theme, toggleTheme] = useThemeSwitcher();
const localValues = useLocalValues(theme, toggleTheme);
const { const {
vegaWalletDialog, vegaWalletDialog,
menu: { setMenuOpen }, menu: { setMenuOpen },
@@ -27,49 +30,31 @@ function App() {
setMenuOpen(false); setMenuOpen(false);
}, [location, setMenuOpen]); }, [location, setMenuOpen]);
const cacheConfig: InMemoryCacheConfig = {
typePolicies: {
Market: {
merge: true,
},
Party: {
merge: true,
},
Query: {},
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Node: {
keyFields: false,
},
Instrument: {
keyFields: false,
},
},
};
return ( return (
<EnvironmentProvider> <EnvironmentProvider>
<NetworkLoader cache={cacheConfig}> <ThemeContext.Provider value={theme}>
<VegaWalletProvider> <NetworkLoader createClient={createClient}>
<LocalContext.Provider value={localValues}> <VegaWalletProvider>
<AppLoader> <LocalContext.Provider value={localValues}>
<div className="max-h-full min-h-full dark:bg-lite-black dark:text-neutral-200 bg-white text-neutral-800 grid grid-rows-[min-content,1fr]"> <AppLoader>
<Header /> <div className="max-h-full min-h-full dark:bg-lite-black dark:text-neutral-200 bg-white text-neutral-800 grid grid-rows-[min-content,1fr]">
<Main /> <Header />
<VegaConnectDialog connectors={Connectors} /> <Main />
<VegaManageDialog <VegaConnectDialog
dialogOpen={vegaWalletDialog.manage} connectors={Connectors}
setDialogOpen={vegaWalletDialog.setManage} dialogOpen={vegaWalletDialog.connect}
/> setDialogOpen={vegaWalletDialog.setConnect}
</div> />
</AppLoader> <VegaManageDialog
</LocalContext.Provider> dialogOpen={vegaWalletDialog.manage}
</VegaWalletProvider> setDialogOpen={vegaWalletDialog.setManage}
</NetworkLoader> />
</div>
</AppLoader>
</LocalContext.Provider>
</VegaWalletProvider>
</NetworkLoader>
</ThemeContext.Provider>
</EnvironmentProvider> </EnvironmentProvider>
); );
} }
@@ -1,109 +0,0 @@
import React, { forwardRef, useCallback, useMemo, useRef } from 'react';
import classNames from 'classnames';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import {
useScreenDimensions,
useThemeSwitcher,
} from '@vegaprotocol/react-helpers';
import type {
GridOptions,
GetRowIdParams,
TabToNextCellParams,
CellKeyDownEvent,
FullWidthCellKeyDownEvent,
IGetRowsParams,
IDatasource,
} from 'ag-grid-community';
import { NO_DATA_MESSAGE } from '../../constants';
import * as constants from '../simple-market-list/constants';
export interface GetRowsParams<T>
extends Omit<IGetRowsParams, 'successCallback'> {
successCallback(rowsThisBlock: T[], lastRow?: number): void;
}
export interface Datasource<T> extends IDatasource {
getRows(params: GetRowsParams<T>): void;
}
interface Props<T> extends GridOptions {
rowData?: T[];
datasource?: Datasource<T>;
handleRowClicked?: (event: { data: T }) => void;
components?: Record<string, unknown>;
classNamesParam?: string | string[];
}
const ConsoleLiteGrid = <T extends { id?: string }>(
{ handleRowClicked, getRowId, classNamesParam, ...props }: Props<T>,
ref?: React.Ref<AgGridReact>
) => {
const { isMobile, screenSize } = useScreenDimensions();
const gridRef = useRef<AgGridReact | null>(null);
const { theme } = useThemeSwitcher();
const handleOnGridReady = useCallback(() => {
(
(ref as React.RefObject<AgGridReact>) || gridRef
).current?.api?.sizeColumnsToFit();
}, [gridRef, ref]);
const onTabToNextCell = useCallback((params: TabToNextCellParams) => {
const {
api,
previousCellPosition: { rowIndex },
} = params;
const rowCount = api.getDisplayedRowCount();
if (rowCount <= rowIndex + 1) {
return null;
}
return { ...params.previousCellPosition, rowIndex: rowIndex + 1 };
}, []);
const getRowIdLocal = useCallback(({ data }: GetRowIdParams) => data.id, []);
const onCellKeyDown = useCallback(
(
params: (CellKeyDownEvent | FullWidthCellKeyDownEvent) & {
event: KeyboardEvent;
}
) => {
const { event: { key = '' } = {}, data } = params;
if (key === 'Enter') {
handleRowClicked?.({ data });
}
},
[handleRowClicked]
);
const shouldSuppressHorizontalScroll = useMemo(() => {
return !isMobile && constants.LARGE_SCREENS.includes(screenSize);
}, [isMobile, screenSize]);
return (
<AgGrid
className={classNames(classNamesParam)}
rowHeight={60}
customThemeParams={
theme === 'dark'
? constants.agGridDarkVariables
: constants.agGridLightVariables
}
onGridReady={handleOnGridReady}
onRowClicked={handleRowClicked}
rowClass={isMobile ? 'mobile' : ''}
rowClassRules={constants.ROW_CLASS_RULES}
ref={ref || gridRef}
overlayNoRowsTemplate={NO_DATA_MESSAGE}
suppressContextMenu
getRowId={getRowId || getRowIdLocal}
suppressMovableColumns
suppressRowTransform
onCellKeyDown={onCellKeyDown}
tabToNextCell={onTabToNextCell}
suppressHorizontalScroll={shouldSuppressHorizontalScroll}
{...props}
/>
);
};
const ConsoleLiteGridForwarder = forwardRef(ConsoleLiteGrid) as <T>(
p: Props<T> & { ref?: React.Ref<AgGridReact> }
) => React.ReactElement;
export default ConsoleLiteGridForwarder;
@@ -1 +0,0 @@
export { default as ConsoleLiteGrid } from './console-lite-grid';
@@ -0,0 +1,14 @@
query PartyBalanceQuery($partyId: ID!) {
party(id: $partyId) {
accounts {
type
balance
asset {
id
symbol
name
decimals
}
}
}
}
@@ -1,54 +1,51 @@
import * as Types from '@vegaprotocol/types'; /* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { gql } from '@apollo/client'; // ====================================================
import * as Apollo from '@apollo/client'; // GraphQL query operation: MarketTags
const defaultOptions = {} as const; // ====================================================
export type MarketTagsQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export interface MarketTags_market_tradableInstrument_instrument_metadata {
export type MarketTagsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null } } } } | null }; __typename: "InstrumentMetadata";
/**
* An arbitrary list of tags to associated to associate to the Instrument (string list)
export const MarketTagsDocument = gql` */
query MarketTags($marketId: ID!) { tags: string[] | null;
market(id: $marketId) {
tradableInstrument {
instrument {
metadata {
tags
}
}
}
}
} }
`;
/** export interface MarketTags_market_tradableInstrument_instrument {
* __useMarketTagsQuery__ __typename: "Instrument";
* /**
* To run a query within a React component, call `useMarketTagsQuery` and pass it any options that fit your needs. * Metadata for this instrument
* When your component renders, `useMarketTagsQuery` returns an object from Apollo Client that contains loading, error, and data properties */
* you can use to render your UI. metadata: MarketTags_market_tradableInstrument_instrument_metadata;
* }
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
* export interface MarketTags_market_tradableInstrument {
* @example __typename: "TradableInstrument";
* const { data, loading, error } = useMarketTagsQuery({ /**
* variables: { * An instance of, or reference to, a fully specified instrument.
* marketId: // value for 'marketId' */
* }, instrument: MarketTags_market_tradableInstrument_instrument;
* }); }
*/
export function useMarketTagsQuery(baseOptions: Apollo.QueryHookOptions<MarketTagsQuery, MarketTagsQueryVariables>) { export interface MarketTags_market {
const options = {...defaultOptions, ...baseOptions} __typename: "Market";
return Apollo.useQuery<MarketTagsQuery, MarketTagsQueryVariables>(MarketTagsDocument, options); /**
} * An instance of, or reference to, a tradable instrument.
export function useMarketTagsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MarketTagsQuery, MarketTagsQueryVariables>) { */
const options = {...defaultOptions, ...baseOptions} tradableInstrument: MarketTags_market_tradableInstrument;
return Apollo.useLazyQuery<MarketTagsQuery, MarketTagsQueryVariables>(MarketTagsDocument, options); }
}
export type MarketTagsQueryHookResult = ReturnType<typeof useMarketTagsQuery>; export interface MarketTags {
export type MarketTagsLazyQueryHookResult = ReturnType<typeof useMarketTagsLazyQuery>; /**
export type MarketTagsQueryResult = Apollo.QueryResult<MarketTagsQuery, MarketTagsQueryVariables>; * An instrument that is trading on the Vega network
*/
market: MarketTags_market | null;
}
export interface MarketTagsVariables {
marketId: string;
}
@@ -0,0 +1,65 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: PartyBalanceQuery
// ====================================================
export interface PartyBalanceQuery_party_accounts_asset {
__typename: "Asset";
/**
* The ID of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The full name of the asset (e.g: Great British Pound)
*/
name: string;
/**
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
*/
decimals: number;
}
export interface PartyBalanceQuery_party_accounts {
__typename: "Account";
/**
* Account type (General, Margin, etc)
*/
type: AccountType;
/**
* Balance as string - current account balance (approx. as balances can be updated several times per second)
*/
balance: string;
/**
* Asset, the 'currency'
*/
asset: PartyBalanceQuery_party_accounts_asset;
}
export interface PartyBalanceQuery_party {
__typename: "Party";
/**
* Collateral accounts relating to a party
*/
accounts: PartyBalanceQuery_party_accounts[] | null;
}
export interface PartyBalanceQuery {
/**
* An entity that is trading on the Vega network
*/
party: PartyBalanceQuery_party | null;
}
export interface PartyBalanceQueryVariables {
partyId: string;
}
@@ -0,0 +1,54 @@
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type MarketTagsQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type MarketTagsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null } } } } | null };
export const MarketTagsDocument = gql`
query MarketTags($marketId: ID!) {
market(id: $marketId) {
tradableInstrument {
instrument {
metadata {
tags
}
}
}
}
}
`;
/**
* __useMarketTagsQuery__
*
* To run a query within a React component, call `useMarketTagsQuery` and pass it any options that fit your needs.
* When your component renders, `useMarketTagsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useMarketTagsQuery({
* variables: {
* marketId: // value for 'marketId'
* },
* });
*/
export function useMarketTagsQuery(baseOptions: Apollo.QueryHookOptions<MarketTagsQuery, MarketTagsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<MarketTagsQuery, MarketTagsQueryVariables>(MarketTagsDocument, options);
}
export function useMarketTagsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MarketTagsQuery, MarketTagsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<MarketTagsQuery, MarketTagsQueryVariables>(MarketTagsDocument, options);
}
export type MarketTagsQueryHookResult = ReturnType<typeof useMarketTagsQuery>;
export type MarketTagsLazyQueryHookResult = ReturnType<typeof useMarketTagsLazyQuery>;
export type MarketTagsQueryResult = Apollo.QueryResult<MarketTagsQuery, MarketTagsQueryVariables>;
@@ -0,0 +1,57 @@
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type PartyBalanceQueryQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type PartyBalanceQueryQuery = { __typename?: 'Query', party?: { __typename?: 'Party', accounts?: Array<{ __typename?: 'Account', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } }> | null } | null };
export const PartyBalanceQueryDocument = gql`
query PartyBalanceQuery($partyId: ID!) {
party(id: $partyId) {
accounts {
type
balance
asset {
id
symbol
name
decimals
}
}
}
}
`;
/**
* __usePartyBalanceQueryQuery__
*
* To run a query within a React component, call `usePartyBalanceQueryQuery` and pass it any options that fit your needs.
* When your component renders, `usePartyBalanceQueryQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = usePartyBalanceQueryQuery({
* variables: {
* partyId: // value for 'partyId'
* },
* });
*/
export function usePartyBalanceQueryQuery(baseOptions: Apollo.QueryHookOptions<PartyBalanceQueryQuery, PartyBalanceQueryQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<PartyBalanceQueryQuery, PartyBalanceQueryQueryVariables>(PartyBalanceQueryDocument, options);
}
export function usePartyBalanceQueryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PartyBalanceQueryQuery, PartyBalanceQueryQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<PartyBalanceQueryQuery, PartyBalanceQueryQueryVariables>(PartyBalanceQueryDocument, options);
}
export type PartyBalanceQueryQueryHookResult = ReturnType<typeof usePartyBalanceQueryQuery>;
export type PartyBalanceQueryLazyQueryHookResult = ReturnType<typeof usePartyBalanceQueryLazyQuery>;
export type PartyBalanceQueryQueryResult = Apollo.QueryResult<PartyBalanceQueryQuery, PartyBalanceQueryQueryVariables>;
@@ -19,19 +19,3 @@ export const EST_FEES_TOOLTIP_TEXT = t(
export const EST_SLIPPAGE = t( export const EST_SLIPPAGE = t(
'When you execute a trade on Vega, the price obtained in the market may differ from the best available price displayed at the time of placing the trade. The estimated slippage shows the difference between the best available price and the estimated execution price, determined by market liquidity and your chosen order size.' 'When you execute a trade on Vega, the price obtained in the market may differ from the best available price displayed at the time of placing the trade. The estimated slippage shows the difference between the best available price and the estimated execution price, determined by market liquidity and your chosen order size.'
); );
export const ERROR_SIZE_DECIMAL = t(
'The size field accepts up to X decimal places'
);
export enum MarketModeValidationType {
PriceMonitoringAuction = 'PriceMonitoringAuction',
LiquidityMonitoringAuction = 'LiquidityMonitoringAuction',
Auction = 'Auction',
}
export enum SummaryValidationType {
NoCollateral = 'NoCollateral',
TradingMode = 'MarketTradingMode',
MarketState = 'MarketState',
}
@@ -1,15 +1,13 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing'; import type {
import type { MarketDealTicketAsset } from '@vegaprotocol/market-list'; PartyBalanceQuery_party_accounts,
PartyBalanceQuery_party_accounts_asset,
} from './__generated__/PartyBalanceQuery';
import { DealTicketBalance } from './deal-ticket-balance'; import { DealTicketBalance } from './deal-ticket-balance';
import { AccountType } from '@vegaprotocol/types';
jest.mock('@vegaprotocol/wallet', () => ({ const tDAI: PartyBalanceQuery_party_accounts_asset = {
...jest.requireActual('@vegaprotocol/wallet'),
useVegaWallet: jest.fn().mockReturnValue('wallet-pub-key'),
}));
const tDAI: MarketDealTicketAsset = {
__typename: 'Asset', __typename: 'Asset',
id: '1', id: '1',
symbol: 'tDAI', symbol: 'tDAI',
@@ -17,20 +15,23 @@ const tDAI: MarketDealTicketAsset = {
decimals: 2, decimals: 2,
}; };
let mockAccountBalance: { const accounts: PartyBalanceQuery_party_accounts[] = [
accountBalance: string; {
accountDecimals: number | null; __typename: 'Account',
} = { accountBalance: '1000000', accountDecimals: 2 }; type: AccountType.ACCOUNT_TYPE_GENERAL,
jest.mock('@vegaprotocol/accounts', () => ({ balance: '1000000',
...jest.requireActual('@vegaprotocol/accounts'), asset: tDAI,
useAccountBalance: jest.fn(() => mockAccountBalance), },
})); ];
describe('DealTicketBalance', function () { describe('DealTicketBalance', function () {
it('should render the balance', () => { it('should render the balance', () => {
const { getByText, getByRole } = render( const { getByText, getByRole } = render(
<DealTicketBalance settlementAsset={tDAI} isWalletConnected />, <DealTicketBalance
{ wrapper: MockedProvider } settlementAsset={tDAI}
accounts={accounts}
isWalletConnected
/>
); );
expect(getByRole('complementary')).toHaveAccessibleName('tDAI Balance'); expect(getByRole('complementary')).toHaveAccessibleName('tDAI Balance');
@@ -40,8 +41,11 @@ describe('DealTicketBalance', function () {
it('should prompt to connect wallet', () => { it('should prompt to connect wallet', () => {
const { getByText } = render( const { getByText } = render(
<DealTicketBalance settlementAsset={tDAI} isWalletConnected={false} />, <DealTicketBalance
{ wrapper: MockedProvider } settlementAsset={tDAI}
accounts={accounts}
isWalletConnected={false}
/>
); );
expect( expect(
@@ -50,10 +54,12 @@ describe('DealTicketBalance', function () {
}); });
it('should display zero balance', () => { it('should display zero balance', () => {
mockAccountBalance = { accountBalance: '', accountDecimals: null };
const { getByText } = render( const { getByText } = render(
<DealTicketBalance settlementAsset={tDAI} isWalletConnected={true} />, <DealTicketBalance
{ wrapper: MockedProvider } settlementAsset={tDAI}
accounts={[]}
isWalletConnected={true}
/>
); );
expect(getByText('No tDAI left to trade')).toBeInTheDocument(); expect(getByText('No tDAI left to trade')).toBeInTheDocument();
@@ -1,39 +1,43 @@
import * as React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { import type { DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset } from '@vegaprotocol/deal-ticket';
addDecimalsFormatNumber, import type { PartyBalanceQuery_party_accounts } from './__generated__/PartyBalanceQuery';
t, import { useSettlementAccount } from '../../hooks/use-settlement-account';
toBigNum, import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
} from '@vegaprotocol/react-helpers'; import { AccountType } from '@vegaprotocol/types';
import type { MarketDealTicket } from '@vegaprotocol/market-list';
import { useAccountBalance } from '@vegaprotocol/accounts';
interface DealTicketBalanceProps { interface DealTicketBalanceProps {
settlementAsset: MarketDealTicket['tradableInstrument']['instrument']['product']['settlementAsset']; settlementAsset: DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset;
accounts: PartyBalanceQuery_party_accounts[];
isWalletConnected: boolean; isWalletConnected: boolean;
className?: string; className?: string;
} }
export const DealTicketBalance = ({ export const DealTicketBalance = ({
settlementAsset, settlementAsset,
accounts,
isWalletConnected, isWalletConnected,
className = '', className = '',
}: DealTicketBalanceProps) => { }: DealTicketBalanceProps) => {
const settlementAssetId = settlementAsset?.id; const settlementAssetId = settlementAsset?.id;
const settlementAssetSymbol = settlementAsset?.symbol; const settlementAssetSymbol = settlementAsset?.symbol;
const settlementAccount = useSettlementAccount(
const { accountBalance, accountDecimals } = settlementAssetId,
useAccountBalance(settlementAssetId) || {}; accounts,
const settlementBalance = toBigNum(accountBalance || 0, accountDecimals || 0); AccountType.ACCOUNT_TYPE_GENERAL
);
const formattedNumber = const formatedNumber =
accountBalance && settlementAccount?.balance &&
accountDecimals && settlementAccount.asset.decimals &&
addDecimalsFormatNumber(accountBalance, accountDecimals); addDecimalsFormatNumber(
settlementAccount.balance,
settlementAccount.asset.decimals
);
const balance = ( const balance = (
<p className="text-blue text-lg font-semibold"> <p className="text-blue text-lg font-semibold">
{!settlementBalance.isZero() {settlementAccount
? t(`${formattedNumber}`) ? t(`${formatedNumber}`)
: `No ${settlementAssetSymbol} left to trade`} : `No ${settlementAssetSymbol} left to trade`}
</p> </p>
); );
@@ -1,75 +1,86 @@
import * as React from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { DealTicketManager } from '@vegaprotocol/deal-ticket'; import { gql, useQuery } from '@apollo/client';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit'; import {
import { t, useDataProvider } from '@vegaprotocol/react-helpers'; DealTicketManager,
DealTicketContainer as Container,
} from '@vegaprotocol/deal-ticket';
import { Loader } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { DealTicketSteps } from './deal-ticket-steps'; import { DealTicketSteps } from './deal-ticket-steps';
import { DealTicketBalance } from './deal-ticket-balance'; import { DealTicketBalance } from './deal-ticket-balance';
import Baubles from './baubles-decor'; import Baubles from './baubles-decor';
import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
import ConnectWallet from '../wallet-connector'; import ConnectWallet from '../wallet-connector';
import { useMemo } from 'react';
import type {
MarketDataUpdateFieldsFragment,
MarketDealTicket,
} from '@vegaprotocol/market-list';
import { marketDealTicketProvider } from '@vegaprotocol/market-list';
const tempEmptyText = ( const tempEmptyText = (
<p>{t('Please select a market from the markets page')}</p> <p>{t('Please select a market from the markets page')}</p>
); );
const PARTY_BALANCE_QUERY = gql`
query PartyBalanceQuery($partyId: ID!) {
party(id: $partyId) {
accounts {
type
balance
asset {
id
symbol
name
decimals
}
}
}
}
`;
export const DealTicketContainer = () => { export const DealTicketContainer = () => {
const { marketId } = useParams<{ marketId: string }>(); const { marketId } = useParams<{ marketId: string }>();
const { pubKey } = useVegaWallet(); const { keypair } = useVegaWallet();
const variables = useMemo( const { data: partyData, loading } = useQuery<PartyBalanceQuery>(
() => ({ PARTY_BALANCE_QUERY,
marketId: marketId || '', {
}), variables: { partyId: keypair?.pub },
[marketId] skip: !keypair?.pub,
}
); );
const { data, loading } = useDataProvider<
MarketDealTicket,
MarketDataUpdateFieldsFragment
>({
dataProvider: marketDealTicketProvider,
variables,
skip: !marketId,
});
const loader = <Loader />; const loader = <Loader />;
if (marketId && data) {
const balance = (
<DealTicketBalance
className="mb-4"
settlementAsset={
data.tradableInstrument.instrument.product?.settlementAsset
}
isWalletConnected={!!pubKey}
/>
);
const container = ( const container = marketId ? (
<DealTicketManager market={data}> <Container marketId={marketId}>
{loading ? loader : balance} {(data) => {
<DealTicketSteps market={data} /> const balance = (
</DealTicketManager> <DealTicketBalance
); className="mb-4"
settlementAsset={
data.market.tradableInstrument.instrument.product?.settlementAsset
}
accounts={partyData?.party?.accounts || []}
isWalletConnected={!!keypair?.pub}
/>
);
return ( return (
<section className="flex p-4 md:p-6"> <DealTicketManager market={data.market}>
<section className="w-full md:w-1/2 md:min-w-[500px]"> {loading ? loader : balance}
{pubKey ? container : <ConnectWallet />} <DealTicketSteps market={data.market} partyData={partyData} />
</section> </DealTicketManager>
<Baubles /> );
</section> }}
); </Container>
}
return marketId ? (
<Splash>
<p>{t('Could not load market')}</p>
</Splash>
) : ( ) : (
tempEmptyText tempEmptyText
); );
return (
<section className="flex p-4 md:p-6">
<section className="w-full md:w-1/2 md:min-w-[500px]">
{keypair ? container : <ConnectWallet />}
</section>
<Baubles />
</section>
);
}; };
@@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import { Icon, Tooltip, TrafficLight } from '@vegaprotocol/ui-toolkit'; import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import { IconNames } from '@blueprintjs/icons'; import { IconNames } from '@blueprintjs/icons';
import * as constants from '../constants'; import * as constants from './constants';
import { TrafficLight } from '../traffic-light';
interface DealTicketEstimatesProps { interface DealTicketEstimatesProps {
quoteName?: string; quoteName?: string;
@@ -16,6 +17,45 @@ interface DealTicketEstimatesProps {
slippage?: string; slippage?: string;
} }
interface DataTitleProps {
children: ReactNode;
quoteName?: string;
}
export const DataTitle = ({ children, quoteName = '' }: DataTitleProps) => (
<dt>
{children}
{quoteName && <small> ({quoteName})</small>}
</dt>
);
interface ValueTooltipProps {
value?: string;
children?: ReactNode;
description: string;
id?: string;
}
export const ValueTooltipRow = ({
value,
children,
description,
id,
}: ValueTooltipProps) => (
<dd className="flex gap-x-2 items-center">
{value || children}
<Tooltip align="center" description={description}>
<div className="cursor-help" id={id || ''} tabIndex={-1}>
<Icon
name={IconNames.ISSUE}
className="block rotate-180"
ariaLabel={description}
/>
</div>
</Tooltip>
</dd>
);
export const DealTicketEstimates = ({ export const DealTicketEstimates = ({
price, price,
quoteName, quoteName,
@@ -91,42 +131,3 @@ export const DealTicketEstimates = ({
)} )}
</dl> </dl>
); );
interface DataTitleProps {
children: ReactNode;
quoteName?: string;
}
export const DataTitle = ({ children, quoteName = '' }: DataTitleProps) => (
<dt>
{children}
{quoteName && <small> ({quoteName})</small>}
</dt>
);
interface ValueTooltipProps {
value?: string;
children?: ReactNode;
description: string;
id?: string;
}
export const ValueTooltipRow = ({
value,
children,
description,
id,
}: ValueTooltipProps) => (
<dd className="flex gap-x-2 items-center">
{value || children}
<Tooltip align="center" description={description}>
<div className="cursor-help" id={id || ''} tabIndex={-1}>
<Icon
name={IconNames.ISSUE}
className="block rotate-180"
ariaLabel={description}
/>
</div>
</Tooltip>
</dd>
);
@@ -1,4 +1,5 @@
import { DealTicketEstimates } from '@vegaprotocol/deal-ticket'; import React from 'react';
import { DealTicketEstimates } from './deal-ticket-estimates';
import { DealTicketSizeInput } from './deal-ticket-size-input'; import { DealTicketSizeInput } from './deal-ticket-size-input';
interface DealTicketSizeProps { interface DealTicketSizeProps {
@@ -1,19 +1,11 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import { import * as constants from './constants';
Dialog, import { TrafficLight } from '../traffic-light';
Icon, import { Dialog, Icon, Intent, Tooltip } from '@vegaprotocol/ui-toolkit';
Intent,
Tooltip,
TrafficLight,
} from '@vegaprotocol/ui-toolkit';
import { InputSetter } from '../../components/input-setter'; import { InputSetter } from '../../components/input-setter';
import { IconNames } from '@blueprintjs/icons'; import { IconNames } from '@blueprintjs/icons';
import { import { DataTitle, ValueTooltipRow } from './deal-ticket-estimates';
DataTitle,
ValueTooltipRow,
EST_SLIPPAGE,
} from '@vegaprotocol/deal-ticket';
interface DealTicketSlippageProps { interface DealTicketSlippageProps {
step?: number; step?: number;
@@ -48,12 +40,12 @@ export const DealTicketSlippage = ({
const formLabel = ( const formLabel = (
<label className="flex items-center mb-1"> <label className="flex items-center mb-1">
<span className="mr-1">{t('Adjust slippage tolerance')}</span> <span className="mr-1">{t('Adjust slippage tolerance')}</span>
<Tooltip align="center" description={EST_SLIPPAGE}> <Tooltip align="center" description={constants.EST_SLIPPAGE}>
<div className="cursor-help" tabIndex={-1}> <div className="cursor-help" tabIndex={-1}>
<Icon <Icon
name={IconNames.ISSUE} name={IconNames.ISSUE}
className="block rotate-180" className="block rotate-180"
ariaLabel={EST_SLIPPAGE} ariaLabel={constants.EST_SLIPPAGE}
/> />
</div> </div>
</Tooltip> </Tooltip>
@@ -91,7 +83,7 @@ export const DealTicketSlippage = ({
<DataTitle>{t('Est. Price Impact / Slippage')}</DataTitle> <DataTitle>{t('Est. Price Impact / Slippage')}</DataTitle>
<div className="flex"> <div className="flex">
<div className="mr-1"> <div className="mr-1">
<ValueTooltipRow description={EST_SLIPPAGE}> <ValueTooltipRow description={constants.EST_SLIPPAGE}>
<TrafficLight value={value} q1={1} q2={5}> <TrafficLight value={value} q1={1} q2={5}>
{value}% {value}%
</TrafficLight> </TrafficLight>
@@ -1,50 +1,50 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import { Stepper } from '../stepper'; import { Stepper } from '../stepper';
import { import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
getDefaultOrder,
useOrderCloseOut,
useOrderMargin,
useMaximumPositionSize,
useCalculateSlippage,
validateAmount,
} from '@vegaprotocol/deal-ticket';
import { InputError } from '@vegaprotocol/ui-toolkit'; import { InputError } from '@vegaprotocol/ui-toolkit';
import { BigNumber } from 'bignumber.js'; import { BigNumber } from 'bignumber.js';
import { MarketSelector } from '@vegaprotocol/deal-ticket'; import { MarketSelector } from '@vegaprotocol/deal-ticket';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import type { Order } from '@vegaprotocol/orders';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet, VegaTxStatus } from '@vegaprotocol/wallet';
import { VegaTxStatus } from '@vegaprotocol/wallet';
import { import {
t, t,
addDecimalsFormatNumber,
toDecimal, toDecimal,
removeDecimal, removeDecimal,
addDecimalsFormatNumber,
addDecimal,
formatNumber,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { import {
getDefaultOrder,
useOrderValidation,
useOrderSubmit, useOrderSubmit,
getOrderDialogTitle, getOrderDialogTitle,
getOrderDialogIntent, getOrderDialogIntent,
getOrderDialogIcon, getOrderDialogIcon,
OrderFeedback, OrderFeedback,
validateSize,
} from '@vegaprotocol/orders'; } from '@vegaprotocol/orders';
import { DealTicketSize } from './deal-ticket-size'; import { DealTicketSize } from './deal-ticket-size';
import MarketNameRenderer from '../simple-market-list/simple-market-renderer'; import MarketNameRenderer from '../simple-market-list/simple-market-renderer';
import SideSelector, { SIDE_NAMES } from './side-selector'; import SideSelector, { SIDE_NAMES } from './side-selector';
import ReviewTrade from './review-trade'; import ReviewTrade from './review-trade';
import * as Schema from '@vegaprotocol/types'; import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
import useOrderCloseOut from '../../hooks/use-order-closeout';
import useOrderMargin from '../../hooks/use-order-margin';
import useMaximumPositionSize from '../../hooks/use-maximum-position-size';
import useCalculateSlippage from '../../hooks/use-calculate-slippage';
import { Side, OrderType } from '@vegaprotocol/types';
import { DealTicketSlippage } from './deal-ticket-slippage'; import { DealTicketSlippage } from './deal-ticket-slippage';
import { useOrderValidation } from './use-order-validation';
import type { MarketDealTicket } from '@vegaprotocol/market-list';
interface DealTicketMarketProps { interface DealTicketMarketProps {
market: MarketDealTicket; market: DealTicketQuery_market;
partyData?: PartyBalanceQuery;
} }
export const DealTicketSteps = ({ market }: DealTicketMarketProps) => { export const DealTicketSteps = ({
market,
partyData,
}: DealTicketMarketProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
const setMarket = useCallback( const setMarket = useCallback(
(marketId: string) => { (marketId: string) => {
@@ -59,23 +59,35 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
watch, watch,
setValue, setValue,
formState: { errors }, formState: { errors },
} = useForm<OrderSubmissionBody['orderSubmission']>({ } = useForm<Order>({
mode: 'onChange', mode: 'onChange',
defaultValues: getDefaultOrder(market), defaultValues: getDefaultOrder(market),
}); });
const emptyString = ' - '; const emptyString = ' - ';
const step = toDecimal(market.positionDecimalPlaces); const step = toDecimal(market.positionDecimalPlaces);
const orderType = watch('type');
const orderTimeInForce = watch('timeInForce');
const orderSide = watch('side');
const orderSize = watch('size');
const order = watch(); const order = watch();
const { pubKey } = useVegaWallet();
const { message: invalidText, isDisabled } = useOrderValidation({ const { message: invalidText, isDisabled } = useOrderValidation({
market, market,
order, orderType,
orderTimeInForce,
fieldErrors: errors, fieldErrors: errors,
}); });
const { submit, transaction, finalizedOrder, Dialog } = useOrderSubmit(); const { submit, transaction, finalizedOrder, Dialog } = useOrderSubmit();
const { keypair } = useVegaWallet();
const estMargin = useOrderMargin({
order,
market,
partyId: keypair?.pub || '',
});
const maxTrade = useMaximumPositionSize({ const maxTrade = useMaximumPositionSize({
partyId: keypair?.pub || '',
accounts: partyData?.party?.accounts || [],
marketId: market.id, marketId: market.id,
settlementAssetId: settlementAssetId:
market.tradableInstrument.instrument.product.settlementAsset.id, market.tradableInstrument.instrument.product.settlementAsset.id,
@@ -83,10 +95,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
order, order,
}); });
const estCloseOut = useOrderCloseOut({ const estCloseOut = useOrderCloseOut({ order, market, partyData });
order,
market,
});
const slippage = useCalculateSlippage({ marketId: market.id, order }); const slippage = useCalculateSlippage({ marketId: market.id, order });
const [slippageValue, setSlippageValue] = useState( const [slippageValue, setSlippageValue] = useState(
slippage ? parseFloat(slippage) : 0 slippage ? parseFloat(slippage) : 0
@@ -103,55 +112,42 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
const price = useMemo(() => { const price = useMemo(() => {
if (slippage && market?.depth?.lastTrade?.price) { if (slippage && market?.depth?.lastTrade?.price) {
const isLong = order.side === Schema.Side.SIDE_BUY; const isLong = order.side === Side.SIDE_BUY;
const multiplier = new BigNumber(1)[isLong ? 'plus' : 'minus']( const multiplier = new BigNumber(1)[isLong ? 'plus' : 'minus'](
parseFloat(slippage) / 100 parseFloat(slippage) / 100
); );
return new BigNumber(market?.depth?.lastTrade?.price) return new BigNumber(market?.depth?.lastTrade?.price)
.multipliedBy(multiplier) .multipliedBy(multiplier)
.toString(); .toNumber();
} }
return undefined; return null;
}, [market?.depth?.lastTrade?.price, order.side, slippage]); }, [market?.depth?.lastTrade?.price, order.side, slippage]);
const estMargin = useOrderMargin({
order,
market,
partyId: pubKey || '',
derivedPrice: price,
});
const formattedPrice = const formattedPrice =
price && addDecimalsFormatNumber(price, market.decimalPlaces); price && addDecimalsFormatNumber(price, market.decimalPlaces);
const notionalSize = useMemo(() => { const notionalSize = useMemo(() => {
if (price) { if (price) {
const size = new BigNumber(price).multipliedBy(order.size).toNumber(); const size = new BigNumber(price).multipliedBy(orderSize).toNumber();
return addDecimalsFormatNumber(size, market.decimalPlaces); return addDecimalsFormatNumber(size, market.decimalPlaces);
} }
return null; return null;
}, [market.decimalPlaces, order.size, price]); }, [market.decimalPlaces, orderSize, price]);
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const fees = useMemo(() => { const fees = useMemo(() => {
if (estMargin?.totalFees && notionalSize) { if (estMargin?.fees && notionalSize) {
const percentage = new BigNumber(estMargin?.totalFees) const percentage = new BigNumber(estMargin?.fees)
.dividedBy(notionalSize) .dividedBy(notionalSize)
.multipliedBy(100) .multipliedBy(100)
.decimalPlaces(2) .decimalPlaces(2)
.toString(); .toString();
return `${addDecimalsFormatNumber( return `${estMargin.fees} (${percentage}%)`;
estMargin.totalFees,
assetDecimals
)} (${formatNumber(addDecimal(percentage, assetDecimals), 2)}%)`;
} }
return null; return null;
}, [assetDecimals, estMargin?.totalFees, notionalSize]); }, [estMargin?.fees, notionalSize]);
const max = useMemo(() => { const max = useMemo(() => {
return new BigNumber(maxTrade) return new BigNumber(maxTrade)
@@ -159,16 +155,12 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
.toNumber(); .toNumber();
}, [market.positionDecimalPlaces, maxTrade]); }, [market.positionDecimalPlaces, maxTrade]);
useEffect(() => {
setSlippageValue(slippage ? parseFloat(slippage) : 0);
}, [slippage]);
const onSizeChange = useCallback( const onSizeChange = useCallback(
(value: number) => { (value: number) => {
const newVal = new BigNumber(value) const newVal = new BigNumber(value)
.decimalPlaces(market.positionDecimalPlaces) .decimalPlaces(market.positionDecimalPlaces)
.toString(); .toString();
const isValid = validateAmount(step, 'Size')(newVal); const isValid = validateSize(step)(newVal);
if (isValid !== 'step') { if (isValid !== 'step') {
setValue('size', newVal); setValue('size', newVal);
} }
@@ -180,7 +172,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
(value: number) => { (value: number) => {
if (market?.depth?.lastTrade?.price) { if (market?.depth?.lastTrade?.price) {
if (value) { if (value) {
const isLong = order.side === Schema.Side.SIDE_BUY; const isLong = order.side === Side.SIDE_BUY;
const multiplier = new BigNumber(1)[isLong ? 'plus' : 'minus']( const multiplier = new BigNumber(1)[isLong ? 'plus' : 'minus'](
value / 100 value / 100
); );
@@ -191,11 +183,11 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
setValue('price', bestAskPrice); setValue('price', bestAskPrice);
if (order.type === Schema.OrderType.TYPE_MARKET) { if (orderType === OrderType.TYPE_MARKET) {
setValue('type', Schema.OrderType.TYPE_LIMIT); setValue('type', OrderType.TYPE_LIMIT);
} }
} else { } else {
setValue('type', Schema.OrderType.TYPE_MARKET); setValue('type', OrderType.TYPE_MARKET);
setValue('price', market?.depth?.lastTrade?.price); setValue('price', market?.depth?.lastTrade?.price);
} }
} }
@@ -205,14 +197,13 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
market.decimalPlaces, market.decimalPlaces,
market?.depth?.lastTrade?.price, market?.depth?.lastTrade?.price,
order.side, order.side,
order.type, orderType,
setSlippageValue,
setValue, setValue,
] ]
); );
const onSubmit = useCallback( const onSubmit = useCallback(
(order: OrderSubmissionBody['orderSubmission']) => { (order: Order) => {
if (transactionStatus !== 'pending') { if (transactionStatus !== 'pending') {
submit({ submit({
...order, ...order,
@@ -253,7 +244,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
)} )}
/> />
), ),
value: SIDE_NAMES[order.side] || '', value: SIDE_NAMES[orderSide] || '',
}, },
{ {
label: t('Choose Position Size'), label: t('Choose Position Size'),
@@ -265,7 +256,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
min={step} min={step}
max={max} max={max}
onSizeChange={onSizeChange} onSizeChange={onSizeChange}
size={new BigNumber(order.size).toNumber()} size={new BigNumber(orderSize).toNumber()}
name="size" name="size"
price={formattedPrice || emptyString} price={formattedPrice || emptyString}
positionDecimalPlaces={market.positionDecimalPlaces} positionDecimalPlaces={market.positionDecimalPlaces}
@@ -274,7 +265,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
.symbol .symbol
} }
notionalSize={notionalSize || emptyString} notionalSize={notionalSize || emptyString}
estCloseOut={estCloseOut || emptyString} estCloseOut={estCloseOut}
fees={fees || emptyString} fees={fees || emptyString}
estMargin={estMargin?.margin || emptyString} estMargin={estMargin?.margin || emptyString}
/> />
@@ -284,9 +275,9 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
/> />
</> </>
) : ( ) : (
t('Loading...') 'loading...'
), ),
value: order.size, value: orderSize,
}, },
{ {
label: t('Review Trade'), label: t('Review Trade'),
@@ -304,7 +295,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
isDisabled={isDisabled} isDisabled={isDisabled}
transactionStatus={transactionStatus} transactionStatus={transactionStatus}
order={order} order={order}
estCloseOut={estCloseOut || emptyString} estCloseOut={estCloseOut}
estMargin={estMargin?.margin || emptyString} estMargin={estMargin?.margin || emptyString}
price={formattedPrice || emptyString} price={formattedPrice || emptyString}
quoteName={ quoteName={
@@ -319,15 +310,9 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
title={getOrderDialogTitle(finalizedOrder?.status)} title={getOrderDialogTitle(finalizedOrder?.status)}
intent={getOrderDialogIntent(finalizedOrder?.status)} intent={getOrderDialogIntent(finalizedOrder?.status)}
icon={getOrderDialogIcon(finalizedOrder?.status)} icon={getOrderDialogIcon(finalizedOrder?.status)}
content={{ >
Complete: ( <OrderFeedback transaction={transaction} order={finalizedOrder} />
<OrderFeedback </Dialog>
transaction={transaction}
order={finalizedOrder}
/>
),
}}
/>
</div> </div>
), ),
disabled: true, disabled: true,
@@ -4,20 +4,39 @@ import {
KeyValueTable, KeyValueTable,
KeyValueTableRow, KeyValueTableRow,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import * as React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { DealTicketEstimates } from '@vegaprotocol/deal-ticket'; import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import type { Order } from '@vegaprotocol/orders';
import { SIDE_NAMES } from './side-selector'; import { SIDE_NAMES } from './side-selector';
import * as Schema from '@vegaprotocol/types'; import SimpleMarketExpires from '../simple-market-list/simple-market-expires';
import { MarketExpires } from '@vegaprotocol/market-info'; import { gql, useQuery } from '@apollo/client';
import type { MarketDealTicket } from '@vegaprotocol/market-list'; import type {
import { useMarketTagsQuery } from './__generated__/MarketTags'; MarketTags,
MarketTagsVariables,
} from './__generated__/MarketTags';
import { DealTicketEstimates } from './deal-ticket-estimates';
import { Side } from '@vegaprotocol/types';
export const MARKET_TAGS_QUERY = gql`
query MarketTags($marketId: ID!) {
market(id: $marketId) {
tradableInstrument {
instrument {
metadata {
tags
}
}
}
}
}
`;
interface Props { interface Props {
market: MarketDealTicket; market: DealTicketQuery_market;
isDisabled: boolean; isDisabled: boolean;
transactionStatus?: string; transactionStatus?: string;
order: OrderSubmissionBody['orderSubmission']; order: Order;
estCloseOut: string; estCloseOut: string;
estMargin: string; estMargin: string;
quoteName: string; quoteName: string;
@@ -39,9 +58,12 @@ export default ({
notionalSize, notionalSize,
slippage, slippage,
}: Props) => { }: Props) => {
const { data: tagsData } = useMarketTagsQuery({ const { data: tagsData } = useQuery<MarketTags, MarketTagsVariables>(
variables: { marketId: market.id }, MARKET_TAGS_QUERY,
}); {
variables: { marketId: market.id },
}
);
return ( return (
<div className="mb-8 text-black dark:text-white" data-testid="review-trade"> <div className="mb-8 text-black dark:text-white" data-testid="review-trade">
@@ -51,10 +73,9 @@ export default ({
<div <div
className={classNames( className={classNames(
{ {
'buyButton dark:buyButtonDark': 'buyButton dark:buyButtonDark': order.side === Side.SIDE_BUY,
order.side === Schema.Side.SIDE_BUY,
'sellButton dark:sellButtonDark': 'sellButton dark:sellButtonDark':
order.side === Schema.Side.SIDE_SELL, order.side === Side.SIDE_SELL,
}, },
'px-2 py-1 inline text-ui-small' 'px-2 py-1 inline text-ui-small'
)} )}
@@ -65,7 +86,7 @@ export default ({
<div> <div>
{tagsData?.market?.tradableInstrument.instrument.metadata {tagsData?.market?.tradableInstrument.instrument.metadata
.tags && ( .tags && (
<MarketExpires <SimpleMarketExpires
tags={ tags={
tagsData?.market.tradableInstrument.instrument.metadata.tags tagsData?.market.tradableInstrument.instrument.metadata.tags
} }
@@ -2,16 +2,16 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormGroup } from '@vegaprotocol/ui-toolkit'; import { FormGroup } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types'; import { Side } from '@vegaprotocol/types';
interface SideSelectorProps { interface SideSelectorProps {
value: Schema.Side; value: Side;
onSelect: (side: Schema.Side) => void; onSelect: (side: Side) => void;
} }
export const SIDE_NAMES: Record<Schema.Side, string> = { export const SIDE_NAMES: Record<Side, string> = {
[Schema.Side.SIDE_BUY]: t('Long'), [Side.SIDE_BUY]: t('Long'),
[Schema.Side.SIDE_SELL]: t('Short'), [Side.SIDE_SELL]: t('Short'),
}; };
export default ({ value, onSelect }: SideSelectorProps) => { export default ({ value, onSelect }: SideSelectorProps) => {
@@ -31,9 +31,9 @@ export default ({ value, onSelect }: SideSelectorProps) => {
className={classNames( className={classNames(
'px-8 py-2', 'px-8 py-2',
'buyButton hover:buyButton dark:buyButtonDark dark:hover:buyButtonDark', 'buyButton hover:buyButton dark:buyButtonDark dark:hover:buyButtonDark',
{ selected: value === Schema.Side.SIDE_BUY } { selected: value === Side.SIDE_BUY }
)} )}
onClick={() => onSelect(Schema.Side.SIDE_BUY)} onClick={() => onSelect(Side.SIDE_BUY)}
> >
{t('Long')} {t('Long')}
</button> </button>
@@ -43,9 +43,9 @@ export default ({ value, onSelect }: SideSelectorProps) => {
className={classNames( className={classNames(
'px-8 py-2', 'px-8 py-2',
'sellButton hover:sellButton dark:sellButtonDark dark:hover:sellButtonDark', 'sellButton hover:sellButton dark:sellButtonDark dark:hover:sellButtonDark',
{ selected: value === Schema.Side.SIDE_SELL } { selected: value === Side.SIDE_SELL }
)} )}
onClick={() => onSelect(Schema.Side.SIDE_SELL)} onClick={() => onSelect(Side.SIDE_SELL)}
> >
{t('Short')} {t('Short')}
</button> </button>
@@ -1,415 +0,0 @@
import * as React from 'react';
import { renderHook } from '@testing-library/react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { MockedProvider } from '@apollo/client/testing';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import * as Schema from '@vegaprotocol/types';
import type { ValidationProps } from './use-order-validation';
import { marketTranslations, useOrderValidation } from './use-order-validation';
import type { MarketDealTicket } from '@vegaprotocol/market-list';
import * as DealTicket from '@vegaprotocol/deal-ticket';
jest.mock('@vegaprotocol/wallet');
jest.mock('@vegaprotocol/deal-ticket', () => {
return {
...jest.requireActual('@vegaprotocol/deal-ticket'),
useOrderMarginValidation: jest.fn(),
};
});
type SettlementAsset =
MarketDealTicket['tradableInstrument']['instrument']['product']['settlementAsset'];
const asset: SettlementAsset = {
__typename: 'Asset',
id: 'asset-id',
symbol: 'asset-symbol',
name: 'asset-name',
decimals: 2,
};
const market: MarketDealTicket = {
id: 'market-id',
decimalPlaces: 2,
positionDecimalPlaces: 1,
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
state: Schema.MarketState.STATE_ACTIVE,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
id: 'instrument-id',
name: 'instrument-name',
code: 'instriment-code',
metadata: {
tags: [],
},
product: {
__typename: 'Future',
quoteName: 'quote-name',
settlementAsset: asset,
dataSourceSpecForTradingTermination: {
id: 'dataSource-id',
},
},
},
},
depth: {
__typename: 'MarketDepth',
lastTrade: {
__typename: 'Trade',
price: '100',
},
},
fees: {
__typename: 'Fees',
factors: {
__typename: 'FeeFactors',
makerFee: '1',
infrastructureFee: '2',
liquidityFee: '3',
},
},
data: {
__typename: 'MarketData',
bestBidPrice: '1605489971',
bestOfferPrice: '1606823730',
markPrice: '1606823730',
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
staticMidPrice: '1606156850',
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
marketState: Schema.MarketState.STATE_ACTIVE,
indicativeVolume: '0',
indicativePrice: '0',
bestStaticBidPrice: '1605489971',
bestStaticOfferPrice: '1606823730',
targetStake: '8561302732',
suppliedStake: '727654170336',
auctionStart: null,
auctionEnd: null,
market: { __typename: 'Market', id: 'market-id' },
},
marketTimestamps: {
__typename: 'MarketTimestamps',
close: null,
open: null,
},
};
const defaultWalletContext = {
pubKey: '111111__111111',
pubKeys: [],
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),
selectPubKey: jest.fn(),
connector: null,
};
const order = {
type: Schema.OrderType.TYPE_MARKET,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
marketId: 'market-id',
side: Schema.Side.SIDE_BUY,
size: '0.1',
};
const defaultOrder = {
market: { ...market },
step: 0.1,
order: {
...order,
},
};
const ERROR = {
KEY_MISSING: 'No public key selected',
KEY_TAINTED: 'Selected public key has been tainted',
MARKET_SUSPENDED: 'Market is currently suspended',
MARKET_INACTIVE: 'Market is no longer active',
MARKET_WAITING: 'Market is not active yet',
MARKET_CONTINUOUS_LIMIT:
'Only limit orders are permitted when market is in auction',
MARKET_CONTINUOUS_TIF:
'Until the auction ends, you can only place GFA, GTT, or GTC limit orders',
FIELD_SIZE_REQ: 'You need to provide a size',
FIELD_SIZE_MIN: `Size cannot be lower than "${defaultOrder.step}"`,
FIELD_PRICE_REQ: 'You need to provide a price',
FIELD_PRICE_MIN: 'The price cannot be negative',
FIELD_PRICE_STEP_NULL: 'Order sizes must be in whole numbers for this market',
FIELD_PRICE_STEP_DECIMAL: `The size field accepts up to ${market.positionDecimalPlaces} decimal places`,
};
function setup(
props?: Partial<ValidationProps>,
context?: Partial<VegaWalletContextShape>
) {
const mockUseVegaWallet = useVegaWallet as jest.Mock;
mockUseVegaWallet.mockReturnValue({ ...defaultWalletContext, context });
return renderHook(() => useOrderValidation({ ...defaultOrder, ...props }), {
wrapper: MockedProvider,
});
}
describe('useOrderValidation', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('Returns empty string when given valid data', () => {
jest.spyOn(DealTicket, 'useOrderMarginValidation').mockReturnValue({
balance: '0',
margin: '100',
balanceError: false,
});
const { result } = setup();
expect(result.current).toStrictEqual({
isDisabled: false,
message: ``,
section: '',
});
});
it('Returns an error message when no keypair found', () => {
jest.spyOn(DealTicket, 'useOrderMarginValidation').mockReturnValue({
balance: '0',
margin: '100',
balanceError: false,
});
const { result } = setup(defaultOrder, { pubKey: null });
expect(result.current).toStrictEqual({
isDisabled: false,
message: ``,
section: '',
});
});
it.each`
state
${Schema.MarketState.STATE_SETTLED}
${Schema.MarketState.STATE_REJECTED}
${Schema.MarketState.STATE_TRADING_TERMINATED}
${Schema.MarketState.STATE_CLOSED}
${Schema.MarketState.STATE_CANCELLED}
`(
'Returns an error message for market state when not accepting orders',
({ state }) => {
const market = {
...defaultOrder.market,
data: { ...defaultOrder.market.data, marketState: state },
};
const { result } = setup({ market });
expect(result.current).toStrictEqual({
isDisabled: true,
message: `This market is ${marketTranslations(
state
)} and not accepting orders`,
section: 'sec-summary',
});
}
);
it.each`
state
${Schema.MarketState.STATE_PENDING}
${Schema.MarketState.STATE_PROPOSED}
`(
'Returns an error message for market state suspended or pending',
({ state }) => {
jest.spyOn(DealTicket, 'useOrderMarginValidation').mockReturnValue({
balance: '0',
margin: '100',
balanceError: false,
});
const market = {
...defaultOrder.market,
data: {
...defaultOrder.market.data,
marketState: state,
marketTradingMode:
Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
},
};
const { result } = setup({
market,
order: {
...order,
type: Schema.OrderType.TYPE_LIMIT,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
},
});
expect(result.current).toStrictEqual({
isDisabled: false,
message: `This market is ${Schema.MarketStateMapping[
state as Schema.MarketState
].toLowerCase()} and only accepting liquidity commitment orders`,
section: 'sec-summary',
});
}
);
it.each`
tradingMode | errorMessage
${Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION} | ${ERROR.MARKET_CONTINUOUS_LIMIT}
${Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION} | ${ERROR.MARKET_CONTINUOUS_LIMIT}
${Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION} | ${ERROR.MARKET_CONTINUOUS_LIMIT}
`(
`Returns an error message when trying to submit a non-limit order for a "$tradingMode" market`,
({ tradingMode, errorMessage }) => {
const market = {
...defaultOrder.market,
data: { ...defaultOrder.market.data, marketTradingMode: tradingMode },
};
const { result } = setup({
market,
order: {
...order,
type: Schema.OrderType.TYPE_MARKET,
},
});
expect(result.current.isDisabled).toBeTruthy();
expect(result.current.message).toBe(errorMessage);
}
);
it.each`
tradingMode | orderTimeInForce | errorMessage
${Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION} | ${Schema.OrderTimeInForce.TIME_IN_FORCE_FOK} | ${ERROR.MARKET_CONTINUOUS_TIF}
${Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION} | ${Schema.OrderTimeInForce.TIME_IN_FORCE_FOK} | ${ERROR.MARKET_CONTINUOUS_TIF}
${Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION} | ${Schema.OrderTimeInForce.TIME_IN_FORCE_FOK} | ${ERROR.MARKET_CONTINUOUS_TIF}
${Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION} | ${Schema.OrderTimeInForce.TIME_IN_FORCE_IOC} | ${ERROR.MARKET_CONTINUOUS_TIF}
${Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION} | ${Schema.OrderTimeInForce.TIME_IN_FORCE_IOC} | ${ERROR.MARKET_CONTINUOUS_TIF}
${Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION} | ${Schema.OrderTimeInForce.TIME_IN_FORCE_IOC} | ${ERROR.MARKET_CONTINUOUS_TIF}
${Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION} | ${Schema.OrderTimeInForce.TIME_IN_FORCE_GFN} | ${ERROR.MARKET_CONTINUOUS_TIF}
${Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION} | ${Schema.OrderTimeInForce.TIME_IN_FORCE_GFN} | ${ERROR.MARKET_CONTINUOUS_TIF}
${Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION} | ${Schema.OrderTimeInForce.TIME_IN_FORCE_GFN} | ${ERROR.MARKET_CONTINUOUS_TIF}
`(
`Returns an error message when submitting a limit order with a "$orderTimeInForce" value to a "$tradingMode" market`,
({ tradingMode, orderTimeInForce, errorMessage }) => {
const market = {
...defaultOrder.market,
data: { ...defaultOrder.market.data, marketTradingMode: tradingMode },
};
const { result } = setup({
market,
order: {
...order,
type: Schema.OrderType.TYPE_LIMIT,
timeInForce: orderTimeInForce,
},
});
expect(result.current).toStrictEqual({
isDisabled: true,
message: errorMessage,
section: 'sec-force',
});
}
);
it.each`
fieldName | errorType | section | errorMessage
${`size`} | ${`required`} | ${'sec-size'} | ${ERROR.FIELD_SIZE_REQ}
${`size`} | ${`min`} | ${'sec-size'} | ${ERROR.FIELD_SIZE_MIN}
${`price`} | ${`required`} | ${'sec-price'} | ${ERROR.FIELD_PRICE_REQ}
${`price`} | ${`min`} | ${'sec-price'} | ${ERROR.FIELD_PRICE_MIN}
`(
`Returns an error message when the order $fieldName "$errorType" validation fails`,
({ fieldName, errorType, section, errorMessage }) => {
const { result } = setup({
fieldErrors: { [fieldName]: { type: errorType } },
order: {
...order,
type: Schema.OrderType.TYPE_LIMIT,
},
});
expect(result.current).toStrictEqual({
isDisabled: true,
message: errorMessage,
section,
});
}
);
it('Returns an error message when the order size incorrectly has decimal values', () => {
const { result } = setup({
market: { ...market, positionDecimalPlaces: 0 },
fieldErrors: {
size: { type: `validate`, message: DealTicket.ERROR_SIZE_DECIMAL },
},
});
expect(result.current).toStrictEqual({
isDisabled: true,
message: ERROR.FIELD_PRICE_STEP_NULL,
section: 'sec-size',
});
});
it('Returns an error message when the order size has more decimals than allowed', () => {
const { result } = setup({
fieldErrors: {
size: { type: `validate`, message: DealTicket.ERROR_SIZE_DECIMAL },
},
});
expect(result.current).toStrictEqual({
isDisabled: true,
message: ERROR.FIELD_PRICE_STEP_DECIMAL,
section: 'sec-size',
});
});
it('Returns an error message when the estimated margin is higher than collateral', async () => {
const invalidatedMockValue = {
balance: '100',
margin: '200',
balanceError: true,
};
jest
.spyOn(DealTicket, 'useOrderMarginValidation')
.mockReturnValue(invalidatedMockValue);
const { result } = setup({});
expect(result.current.isDisabled).toBe(false);
const testElement = (
<DealTicket.MarginWarning
margin={invalidatedMockValue.margin}
balance={invalidatedMockValue.balance}
asset={asset}
/>
);
expect((result.current.message as React.ReactElement)?.props).toEqual(
testElement.props
);
expect((result.current.message as React.ReactElement)?.type).toEqual(
testElement.type
);
});
it.each`
state
${Schema.MarketState.STATE_PENDING}
${Schema.MarketState.STATE_PROPOSED}
`(
'Returns error when market state is pending and size is wrong',
({ state }) => {
const market = {
...defaultOrder.market,
data: { ...defaultOrder.market.data, marketState: state },
};
const { result } = setup({
fieldErrors: {
size: { type: `validate`, message: DealTicket.ERROR_SIZE_DECIMAL },
},
market,
});
expect(result.current).toStrictEqual({
isDisabled: true,
message: ERROR.FIELD_PRICE_STEP_DECIMAL,
section: 'sec-size',
});
}
);
});
@@ -1,384 +0,0 @@
import type { ReactNode } from 'react';
import type { FieldErrors } from 'react-hook-form';
import { useMemo } from 'react';
import { t, toDecimal } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import * as Schema from '@vegaprotocol/types';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import {
MarketDataGrid,
compileGridData,
MarginWarning,
isMarketInAuction,
ERROR_SIZE_DECIMAL,
useOrderMarginValidation,
} from '@vegaprotocol/deal-ticket';
import type { MarketDealTicket } from '@vegaprotocol/market-list';
export const DEAL_TICKET_SECTION = {
TYPE: 'sec-type',
SIZE: 'sec-size',
PRICE: 'sec-price',
FORCE: 'sec-force',
EXPIRY: 'sec-expiry',
SUMMARY: 'sec-summary',
};
export const ERROR_EXPIRATION_IN_THE_PAST = 'ERROR_EXPIRATION_IN_THE_PAST';
export type ValidationProps = {
step?: number;
market: MarketDealTicket;
order: OrderSubmissionBody['orderSubmission'];
fieldErrors?: FieldErrors<OrderSubmissionBody['orderSubmission']>;
};
export const marketTranslations = (marketState: Schema.MarketState) => {
switch (marketState) {
case Schema.MarketState.STATE_TRADING_TERMINATED:
return t('terminated');
default:
return t(Schema.MarketStateMapping[marketState]).toLowerCase();
}
};
export type DealTicketSection =
| ''
| typeof DEAL_TICKET_SECTION[keyof typeof DEAL_TICKET_SECTION];
export const useOrderValidation = ({
market,
fieldErrors,
order,
}: ValidationProps): {
message: ReactNode | string;
isDisabled: boolean;
section: DealTicketSection;
} => {
const { pubKey } = useVegaWallet();
const minSize = toDecimal(market.positionDecimalPlaces);
const isInvalidOrderMargin = useOrderMarginValidation({ market, order });
const fieldErrorChecking = useMemo<{
message: ReactNode | string;
isDisabled: boolean;
section: DealTicketSection;
} | null>(() => {
if (fieldErrors?.size?.type || fieldErrors?.price?.type) {
if (fieldErrors?.size?.type === 'required') {
return {
isDisabled: true,
message: t('You need to provide a size'),
section: DEAL_TICKET_SECTION.SIZE,
};
}
if (fieldErrors?.size?.type === 'min') {
return {
isDisabled: true,
message: t(`Size cannot be lower than "${minSize}"`),
section: DEAL_TICKET_SECTION.SIZE,
};
}
if (
fieldErrors?.price?.type === 'required' &&
order.type !== Schema.OrderType.TYPE_MARKET
) {
return {
isDisabled: true,
message: t('You need to provide a price'),
section: DEAL_TICKET_SECTION.PRICE,
};
}
if (
fieldErrors?.price?.type === 'min' &&
order.type !== Schema.OrderType.TYPE_MARKET
) {
return {
isDisabled: true,
message: t(`The price cannot be negative`),
section: DEAL_TICKET_SECTION.PRICE,
};
}
if (
fieldErrors?.size?.type === 'validate' &&
fieldErrors?.size?.message === ERROR_SIZE_DECIMAL
) {
if (market.positionDecimalPlaces === 0) {
return {
isDisabled: true,
message: t('Order sizes must be in whole numbers for this market'),
section: DEAL_TICKET_SECTION.SIZE,
};
}
return {
isDisabled: true,
message: t(
`The size field accepts up to ${market.positionDecimalPlaces} decimal places`
),
section: DEAL_TICKET_SECTION.SIZE,
};
}
}
if (
fieldErrors?.expiresAt?.type === 'validate' &&
fieldErrors?.expiresAt.message === ERROR_EXPIRATION_IN_THE_PAST
) {
return {
isDisabled: false,
message: t(
'The expiry date that you have entered appears to be in the past'
),
section: DEAL_TICKET_SECTION.EXPIRY,
};
}
return null;
}, [
fieldErrors?.size?.type,
fieldErrors?.size?.message,
fieldErrors?.price?.type,
fieldErrors?.expiresAt?.type,
fieldErrors?.expiresAt?.message,
order.type,
minSize,
market.positionDecimalPlaces,
]);
const { message, isDisabled, section } = useMemo<{
message: ReactNode | string;
isDisabled: boolean;
section: DealTicketSection;
}>(() => {
if (!pubKey) {
return {
message: t('No public key selected'),
isDisabled: true,
section: DEAL_TICKET_SECTION.SUMMARY,
};
}
if (
[
Schema.MarketState.STATE_SETTLED,
Schema.MarketState.STATE_REJECTED,
Schema.MarketState.STATE_TRADING_TERMINATED,
Schema.MarketState.STATE_CANCELLED,
Schema.MarketState.STATE_CLOSED,
].includes(market.data.marketState)
) {
return {
isDisabled: true,
message: t(
`This market is ${marketTranslations(
market.data.marketState
)} and not accepting orders`
),
section: DEAL_TICKET_SECTION.SUMMARY,
};
}
if (
[
Schema.MarketState.STATE_PROPOSED,
Schema.MarketState.STATE_PENDING,
].includes(market.data.marketState)
) {
if (fieldErrorChecking) {
return fieldErrorChecking;
}
return {
isDisabled: false,
message: t(
`This market is ${marketTranslations(
market.data.marketState
)} and only accepting liquidity commitment orders`
),
section: DEAL_TICKET_SECTION.SUMMARY,
};
}
if (isMarketInAuction(market)) {
if (order.type === Schema.OrderType.TYPE_MARKET) {
if (
market.data.marketTradingMode ===
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data.trigger ===
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
) {
return {
isDisabled: true,
message: (
<span>
{t('This market is in auction until it reaches')}{' '}
<Tooltip
description={
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('sufficient liquidity')}</span>
</Tooltip>
{'. '}
{t('Only limit orders are permitted when market is in auction')}
</span>
),
section: DEAL_TICKET_SECTION.TYPE,
};
}
if (
market.data.marketTradingMode ===
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data.trigger === Schema.AuctionTrigger.AUCTION_TRIGGER_PRICE
) {
return {
isDisabled: true,
message: (
<span>
{t('This market is in auction due to')}{' '}
<Tooltip
description={
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('high price volatility')}</span>
</Tooltip>
{'. '}
{t('Only limit orders are permitted when market is in auction')}
</span>
),
section: DEAL_TICKET_SECTION.TYPE,
};
}
return {
isDisabled: true,
message: t(
'Only limit orders are permitted when market is in auction'
),
section: DEAL_TICKET_SECTION.SUMMARY,
};
}
if (
order.type === Schema.OrderType.TYPE_LIMIT &&
[
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
Schema.OrderTimeInForce.TIME_IN_FORCE_GFN,
].includes(order.timeInForce)
) {
if (
market.data.marketTradingMode ===
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data.trigger ===
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
) {
return {
isDisabled: true,
message: (
<span>
{t('This market is in auction until it reaches')}{' '}
<Tooltip
description={
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('sufficient liquidity')}</span>
</Tooltip>
{'. '}
{t(
`Until the auction ends, you can only place GFA, GTT, or GTC limit orders`
)}
</span>
),
section: DEAL_TICKET_SECTION.FORCE,
};
}
if (
market.data.marketTradingMode ===
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data.trigger === Schema.AuctionTrigger.AUCTION_TRIGGER_PRICE
) {
return {
isDisabled: true,
message: (
<span>
{t('This market is in auction due to')}{' '}
<Tooltip
description={
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('high price volatility')}</span>
</Tooltip>
{'. '}
{t(
`Until the auction ends, you can only place GFA, GTT, or GTC limit orders`
)}
</span>
),
section: DEAL_TICKET_SECTION.FORCE,
};
}
return {
isDisabled: true,
message: t(
`Until the auction ends, you can only place GFA, GTT, or GTC limit orders`
),
section: DEAL_TICKET_SECTION.FORCE,
};
}
}
if (fieldErrorChecking) {
return fieldErrorChecking;
}
if (isInvalidOrderMargin.balanceError) {
return {
isDisabled: false,
message: (
<MarginWarning
margin={isInvalidOrderMargin.margin}
balance={isInvalidOrderMargin.balance}
asset={market.tradableInstrument.instrument.product.settlementAsset}
/>
),
section: DEAL_TICKET_SECTION.PRICE,
};
}
if (
[
Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
].includes(market.data.marketTradingMode)
) {
return {
isDisabled: false,
message: t(
'Any orders placed now will not trade until the auction ends'
),
section: DEAL_TICKET_SECTION.SUMMARY,
};
}
return {
isDisabled: false,
message: '',
section: '',
};
}, [
pubKey,
market,
fieldErrorChecking,
isInvalidOrderMargin,
order.type,
order.timeInForce,
]);
return { message, isDisabled, section };
};
@@ -0,0 +1,17 @@
query Deposits {
assetsConnection {
edges {
node {
id
name
symbol
decimals
source {
... on ERC20 {
contractAddress
}
}
}
}
}
}
@@ -0,0 +1,72 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { AssetStatus } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: Deposits
// ====================================================
export interface Deposits_assetsConnection_edges_node_source_BuiltinAsset {
__typename: "BuiltinAsset";
}
export interface Deposits_assetsConnection_edges_node_source_ERC20 {
__typename: "ERC20";
/**
* The address of the ERC20 contract
*/
contractAddress: string;
}
export type Deposits_assetsConnection_edges_node_source = Deposits_assetsConnection_edges_node_source_BuiltinAsset | Deposits_assetsConnection_edges_node_source_ERC20;
export interface Deposits_assetsConnection_edges_node {
__typename: "Asset";
/**
* The ID of the asset
*/
id: string;
/**
* The full name of the asset (e.g: Great British Pound)
*/
name: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
*/
decimals: number;
/**
* The status of the asset in the Vega network
*/
status: AssetStatus;
/**
* The origin source of the asset (e.g: an ERC20 asset)
*/
source: Deposits_assetsConnection_edges_node_source;
}
export interface Deposits_assetsConnection_edges {
__typename: "AssetEdge";
node: Deposits_assetsConnection_edges_node;
}
export interface Deposits_assetsConnection {
__typename: "AssetsConnection";
/**
* The assets
*/
edges: (Deposits_assetsConnection_edges | null)[] | null;
}
export interface Deposits {
/**
* The list of all assets in use in the Vega network or the specified asset if ID is provided
*/
assetsConnection: Deposits_assetsConnection | null;
}
@@ -0,0 +1,57 @@
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type DepositsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type DepositsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, source: { __typename?: 'BuiltinAsset' } | { __typename?: 'ERC20', contractAddress: string } } } | null> | null } | null };
export const DepositsDocument = gql`
query Deposits {
assetsConnection {
edges {
node {
id
name
symbol
decimals
source {
... on ERC20 {
contractAddress
}
}
}
}
}
}
`;
/**
* __useDepositsQuery__
*
* To run a query within a React component, call `useDepositsQuery` and pass it any options that fit your needs.
* When your component renders, `useDepositsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useDepositsQuery({
* variables: {
* },
* });
*/
export function useDepositsQuery(baseOptions?: Apollo.QueryHookOptions<DepositsQuery, DepositsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<DepositsQuery, DepositsQueryVariables>(DepositsDocument, options);
}
export function useDepositsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<DepositsQuery, DepositsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<DepositsQuery, DepositsQueryVariables>(DepositsDocument, options);
}
export type DepositsQueryHookResult = ReturnType<typeof useDepositsQuery>;
export type DepositsLazyQueryHookResult = ReturnType<typeof useDepositsLazyQuery>;
export type DepositsQueryResult = Apollo.QueryResult<DepositsQuery, DepositsQueryVariables>;
@@ -1,18 +1,61 @@
import { t } from '@vegaprotocol/react-helpers'; import { gql, useQuery } from '@apollo/client';
import { Button } from '@vegaprotocol/ui-toolkit'; import { DepositManager } from '@vegaprotocol/deposits';
import { DepositDialog, useDepositDialog } from '@vegaprotocol/deposits'; import { getEnabledAssets, t } from '@vegaprotocol/react-helpers';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { Web3Container } from '@vegaprotocol/web3';
import type { Deposits } from './__generated__/Deposits';
const DEPOSITS_QUERY = gql`
query Deposits {
assetsConnection {
edges {
node {
id
name
symbol
decimals
status
source {
... on ERC20 {
contractAddress
}
}
}
}
}
}
`;
/** /**
* Fetches data required for the Deposit page * Fetches data required for the Deposit page
*/ */
export const DepositContainer = () => { export const DepositContainer = () => {
const openDepositDialog = useDepositDialog((state) => state.open); const { VEGA_ENV } = useEnvironment();
const { keypair } = useVegaWallet();
const { data, loading, error } = useQuery<Deposits>(DEPOSITS_QUERY, {
variables: { partyId: keypair?.pub },
skip: !keypair?.pub,
});
const assets = getEnabledAssets(data);
return ( return (
<div> <AsyncRenderer<Deposits> data={data} loading={loading} error={error}>
<DepositDialog /> {assets.length ? (
<Button size="sm" onClick={() => openDepositDialog()}> <Web3Container>
{t('Make deposit')} <DepositManager
</Button> assets={assets}
</div> isFaucetable={VEGA_ENV !== Networks.MAINNET}
/>
</Web3Container>
) : (
<Splash>
<p>{t('No assets on this network')}</p>
</Splash>
)}
</AsyncRenderer>
); );
}; };
@@ -1,29 +1,32 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit'; import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import Logo from './logo'; import Logo from './logo';
import { VegaWalletConnectButton } from '../vega-wallet-connect-button'; import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
import LocalContext from '../../context/local-context'; import LocalContext from '../../context/local-context';
const Header = () => { const Header = () => {
const { updateVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
updateVegaWalletDialog: store.updateVegaWalletDialog,
}));
const { const {
vegaWalletDialog: { setManage }, vegaWalletDialog: { setConnect, setManage },
theme,
toggleTheme,
} = useContext(LocalContext); } = useContext(LocalContext);
return ( return (
<div <div
className="dark flex items-stretch pr-6 py-6 bg-black text-neutral-400" className="flex items-stretch pr-6 py-6 bg-black text-neutral-400"
data-testid="header" data-testid="header"
> >
<Logo /> <Logo />
<div className="flex items-center gap-2 ml-auto relative z-10"> <div className="flex items-center gap-2 ml-auto relative z-10">
<VegaWalletConnectButton <VegaWalletConnectButton
setConnectDialog={updateVegaWalletDialog} setConnectDialog={setConnect}
setManageDialog={setManage} setManageDialog={setManage}
/> />
<ThemeSwitcher className="-my-4" /> <ThemeSwitcher
theme={theme}
onToggle={toggleTheme}
className="-my-4"
fixedBg="dark"
/>
</div> </div>
</div> </div>
); );
@@ -1,103 +0,0 @@
import classNames from 'classnames';
import React, { useEffect, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
export interface Item {
name: string;
id: string;
url?: string;
isActive?: boolean;
color?: string;
cssClass?: string;
}
interface Props {
active?: string;
cssClass?: string[];
items: Item[];
'data-testid'?: string;
'aria-label'?: string;
}
const MenuItem = ({ id, name, url, isActive, cssClass }: Item): JSX.Element => {
if (!url) {
return <span data-testid={id}>{name}</span>;
}
return (
<Link
to={url}
aria-label={name}
className={classNames('pl-0 hover:opacity-75', cssClass, {
active: isActive,
})}
data-testid={id}
>
{name}
</Link>
);
};
const findActive = (active?: string, items?: Item[]) => {
if (!active && items?.length) {
return 0;
}
return items?.length
? items.findIndex((item) => item.id === active) || 0
: -1;
};
export const HorizontalMenu = ({
items,
active,
'data-testid': dataTestId,
'aria-label': ariaLabel,
}: Props) => {
const [activeNumber, setActiveNumber] = useState<number>(
findActive(active, items)
);
const slideContRef = useRef<HTMLUListElement | null>(null);
const [sliderStyles, setSliderStyles] = useState<Record<string, string>>({});
useEffect(() => {
setActiveNumber(findActive(active, items));
}, [active, items]);
useEffect(() => {
const contStyles = (
slideContRef.current as HTMLUListElement
).getBoundingClientRect();
const selectedStyles = (slideContRef.current as HTMLUListElement).children[
activeNumber
]?.getBoundingClientRect();
const styles: Record<string, string> = selectedStyles
? {
backgroundColor: items[activeNumber].color || '',
width: `${selectedStyles.width}px`,
left: `${selectedStyles.left - contStyles.left}px`,
}
: {};
setSliderStyles(styles);
}, [activeNumber, slideContRef, items]);
return items.length ? (
<ul
ref={slideContRef}
className="grid grid-flow-col auto-cols-min gap-4 relative pb-2 mb-2"
data-testid={dataTestId}
aria-label={ariaLabel}
>
{items.map((item, i) => (
<li key={item.id} className="md:mr-2 whitespace-nowrap">
<MenuItem {...item} isActive={i === activeNumber} />
</li>
))}
<li
className="absolute bottom-0 h-[2px] transition-left duration-300 dark:bg-white bg-black"
key="slider"
style={sliderStyles}
/>
</ul>
) : null;
};
export default HorizontalMenu;
@@ -1,2 +0,0 @@
export { default as HorizontalMenu } from './horizontal-menu';
export type { Item as HorizontalMenuItem } from './horizontal-menu';
@@ -1,77 +0,0 @@
import { useMemo, useRef, useCallback } from 'react';
import { useOutletContext } from 'react-router-dom';
import type { AgGridReact } from 'ag-grid-react';
import { PriceCell, useDataProvider } from '@vegaprotocol/react-helpers';
import type { AccountFields } from '@vegaprotocol/accounts';
import { aggregatedAccountsDataProvider, getId } from '@vegaprotocol/accounts';
import type { IGetRowsParams } from 'ag-grid-community';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import {
AssetDetailsDialog,
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
import { NO_DATA_MESSAGE } from '../../../constants';
import { ConsoleLiteGrid } from '../../console-lite-grid';
import { useAccountColumnDefinitions } from '.';
const AccountsManager = () => {
const { partyId = '' } = useOutletContext<{ partyId: string }>();
const { isOpen, id, setOpen } = useAssetDetailsDialogStore();
const gridRef = useRef<AgGridReact | null>(null);
const dataRef = useRef<AccountFields[] | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
const update = useCallback(
({ data }: { data: AccountFields[] | null }) => {
dataRef.current = data;
gridRef.current?.api?.refreshInfiniteCache();
return true;
},
[gridRef]
);
const { data, error, loading } = useDataProvider<AccountFields[], never>({
dataProvider: aggregatedAccountsDataProvider,
update,
variables,
});
const getRows = async ({
successCallback,
startRow,
endRow,
}: IGetRowsParams) => {
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow)
: [];
const lastRow = dataRef.current?.length ?? -1;
successCallback(rowsThisBlock, lastRow);
};
const { columnDefs, defaultColDef } = useAccountColumnDefinitions();
console.log(data, loading);
return (
<>
<AsyncRenderer
loading={loading}
error={error}
data={data?.length ? data : null}
noDataMessage={NO_DATA_MESSAGE}
>
<ConsoleLiteGrid<AccountFields>
rowData={data?.length ? undefined : []}
rowModelType={data?.length ? 'infinite' : 'clientSide'}
ref={gridRef}
datasource={{ getRows }}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
components={{ PriceCell }}
getRowId={({ data }) => getId(data)}
/>
</AsyncRenderer>
<AssetDetailsDialog
assetId={id}
open={isOpen}
onChange={(open) => setOpen(open)}
/>
</>
);
};
export default AccountsManager;
@@ -1,2 +0,0 @@
export { default as AccountManager } from './accounts';
export { default as useAccountColumnDefinitions } from './use-column-definitions';
@@ -1,80 +0,0 @@
import React, { useMemo } from 'react';
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
import type { AccountFields } from '@vegaprotocol/accounts';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import type { ColDef, GroupCellRendererParams } from 'ag-grid-community';
import type { VegaValueFormatterParams } from '@vegaprotocol/ui-toolkit';
const useAccountColumnDefinitions = () => {
const { open } = useAssetDetailsDialogStore();
const columnDefs: ColDef[] = useMemo(() => {
return [
{
colId: 'account-asset',
headerName: t('Asset'),
field: 'asset.symbol',
headerClass: 'uppercase justify-start',
cellClass: 'uppercase flex h-full items-center md:pl-4',
cellRenderer: ({ value, data }: GroupCellRendererParams) =>
value && value.length > 0 ? (
<div className="md:pl-4 grid h-full items-center" title={value}>
<div className="truncate min-w-0">
<button
className="hover:underline"
onClick={() => {
open(data.asset.id);
}}
>
{value}
</button>
</div>
</div>
) : (
''
),
},
{
colId: 'deposited',
headerName: t('Deposited'),
field: 'deposited',
cellRenderer: 'PriceCell',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<AccountFields, 'deposited'>) => {
if (value && data) {
return addDecimalsFormatNumber(value, data.asset.decimals);
}
return '-';
},
},
{
colId: 'used',
headerName: t('Used'),
field: 'used',
cellRenderer: 'PriceCell',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<AccountFields, 'used'>) => {
if (value && data) {
return addDecimalsFormatNumber(value, data.asset.decimals);
}
return '-';
},
},
];
}, [open]);
const defaultColDef = useMemo(() => {
return {
sortable: true,
unSortIcon: true,
headerClass: 'uppercase',
editable: false,
};
}, []);
return { columnDefs, defaultColDef };
};
export default useAccountColumnDefinitions;
@@ -1,35 +0,0 @@
import { t } from '@vegaprotocol/react-helpers';
export const PORTFOLIO_ASSETS = 'assets';
export const PORTFOLIO_POSITIONS = 'positions';
export const PORTFOLIO_ORDERS = 'orders';
export const PORTFOLIO_FILLS = 'fills';
export const PORTFOLIO_DEPOSITS = 'deposits';
export const PORTFOLIO_ITEMS = [
{
name: t('Assets'),
id: PORTFOLIO_ASSETS,
url: `/portfolio/${PORTFOLIO_ASSETS}`,
},
{
name: t('Positions'),
id: PORTFOLIO_POSITIONS,
url: `/portfolio/${PORTFOLIO_POSITIONS}`,
},
{
name: t('Orders'),
id: PORTFOLIO_ORDERS,
url: `/portfolio/${PORTFOLIO_ORDERS}`,
},
{
name: t('Fills'),
id: PORTFOLIO_FILLS,
url: `/portfolio/${PORTFOLIO_FILLS}`,
},
{
name: t('Deposits'),
id: PORTFOLIO_DEPOSITS,
url: `/portfolio/${PORTFOLIO_DEPOSITS}`,
},
];
@@ -1,53 +0,0 @@
import { useRef } from 'react';
import { useOutletContext } from 'react-router-dom';
import type { AgGridReact } from 'ag-grid-react';
import type { Trade } from '@vegaprotocol/fills';
import { useFillsList } from '@vegaprotocol/fills';
import type { BodyScrollEndEvent, BodyScrollEvent } from 'ag-grid-community';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { ConsoleLiteGrid } from '../../console-lite-grid';
import { NO_DATA_MESSAGE } from '../../../constants';
import useColumnDefinitions from './use-column-definitions';
const FillsManager = () => {
const { partyId } = useOutletContext<{ partyId: string }>();
const { columnDefs, defaultColDef } = useColumnDefinitions({ partyId });
const gridRef = useRef<AgGridReact | null>(null);
const scrolledToTop = useRef(true);
const { data, error, loading, addNewRows, getRows } = useFillsList({
partyId,
gridRef,
scrolledToTop,
});
const onBodyScrollEnd = (event: BodyScrollEndEvent) => {
if (event.top === 0) {
addNewRows();
}
};
const onBodyScroll = (event: BodyScrollEvent) => {
scrolledToTop.current = event.top <= 0;
};
return (
<AsyncRenderer
loading={loading}
error={error}
data={data?.length ? data : null}
noDataMessage={NO_DATA_MESSAGE}
>
<ConsoleLiteGrid<Trade>
ref={gridRef}
rowModelType="infinite"
datasource={{ getRows }}
onBodyScrollEnd={onBodyScrollEnd}
onBodyScroll={onBodyScroll}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
/>
</AsyncRenderer>
);
};
export default FillsManager;
@@ -1 +0,0 @@
export { default as FillsManager } from './fills';
@@ -1,190 +0,0 @@
import { useMemo } from 'react';
import type { ColDef, ValueFormatterParams } from 'ag-grid-community';
import type { Trade } from '@vegaprotocol/fills';
import classNames from 'classnames';
import {
addDecimal,
addDecimalsFormatNumber,
formatNumber,
getDateTimeFormat,
negativeClassNames,
positiveClassNames,
t,
} from '@vegaprotocol/react-helpers';
import BigNumber from 'bignumber.js';
import * as Schema from '@vegaprotocol/types';
interface Props {
partyId: string;
}
const useColumnDefinitions = ({ partyId }: Props) => {
const columnDefs: ColDef[] = useMemo(() => {
return [
{
colId: 'market',
headerName: t('Market'),
field: 'market.tradableInstrument.instrument.name',
cellClass: '!flex h-full items-center !md:pl-4',
minWidth: 150,
},
{
colId: 'size',
headerName: t('Size'),
headerClass: 'uppercase',
field: 'size',
width: 100,
cellClass: ({ data }: { data: Trade }) => {
return classNames('!flex h-full items-center justify-center', {
[positiveClassNames]: data?.buyer.id === partyId,
[negativeClassNames]: data?.seller.id,
});
},
valueFormatter: ({ value, data }: ValueFormatterParams) => {
if (value && data?.market) {
let prefix;
if (data.buyer.id === partyId) {
prefix = '+';
} else if (data.seller.id) {
prefix = '-';
}
const size = addDecimalsFormatNumber(
value,
data.market.positionDecimalPlaces
);
return `${prefix}${size}`;
}
return '-';
},
},
{
colId: 'value',
headerName: t('Value'),
headerClass: 'uppercase text-center',
cellClass: '!flex h-full items-center',
field: 'price',
valueFormatter: ({ value, data }: ValueFormatterParams) => {
if (value && data?.market) {
const asset =
data.market.tradableInstrument.instrument.product.settlementAsset
.symbol;
const valueFormatted = addDecimalsFormatNumber(
value,
data.market.decimalPlaces
);
return `${valueFormatted} ${asset}`;
}
return '-';
},
type: 'rightAligned',
},
{
colId: 'filledvalue',
headerName: t('Filled value'),
headerClass: 'uppercase text-center',
cellClass: '!flex h-full items-center',
field: 'price',
valueFormatter: ({ value, data }: ValueFormatterParams) => {
if (value && data?.market) {
const asset =
data.market.tradableInstrument.instrument.product.settlementAsset
.symbol;
const size = new BigNumber(
addDecimal(data.size, data.market.positionDecimalPlaces)
);
const price = new BigNumber(
addDecimal(value, data.market.decimalPlaces)
);
const total = size.times(price).toString();
const valueFormatted = formatNumber(
total,
data?.market.decimalPlaces
);
return `${valueFormatted} ${asset}`;
}
return '-';
},
type: 'rightAligned',
},
{
colId: 'role',
headerName: t('Role'),
field: 'aggressor',
width: 100,
valueFormatter: ({ value, data }: ValueFormatterParams) => {
if (value && data) {
const taker = t('Taker');
const maker = t('Maker');
if (data?.buyer.id === partyId) {
if (value === Schema.Side.SIDE_BUY) {
return taker;
} else {
return maker;
}
} else if (data?.seller.id === partyId) {
if (value === Schema.Side.SIDE_SELL) {
return taker;
} else {
return maker;
}
}
}
return '-';
},
},
{
colId: 'fee',
headerName: t('Fee'),
headerClass: 'uppercase text-center',
cellClass: '!flex h-full items-center',
field: 'market.tradableInstrument.instrument.product',
valueFormatter: ({ value, data }: ValueFormatterParams) => {
if (value && data) {
const asset = value.settlementAsset;
let feesObj;
if (data.buyer.id === partyId) {
feesObj = data.buyerFee;
} else if (data?.seller.id === partyId) {
feesObj = data?.sellerFee;
} else {
return '-';
}
const fee = new BigNumber(feesObj.makerFee)
.plus(feesObj.infrastructureFee)
.plus(feesObj.liquidityFee);
const totalFees = addDecimalsFormatNumber(
fee.toString(),
asset.decimals
);
return `${totalFees} ${asset.symbol}`;
}
return '-';
},
type: 'rightAligned',
},
{
colId: 'date',
headerName: t('Date'),
field: 'createdAt',
valueFormatter: ({ value }: ValueFormatterParams) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
},
},
];
}, [partyId]);
const defaultColDef = useMemo(() => {
return {
sortable: true,
unSortIcon: true,
headerClass: 'uppercase',
cellClass: '!flex h-full items-center',
};
}, []);
return { columnDefs, defaultColDef };
};
export default useColumnDefinitions;

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