diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md index 336052ddd..15ffe688e 100644 --- a/.github/ISSUE_TEMPLATE/release.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -1,15 +1,14 @@ --- name: Release -about: - A template to outline the steps needed to for a successful release of our frontend apps +about: A template to outline the steps needed to for a successful release of our frontend apps title: 'Release [add dapp version]-core-[add core version]' -labels: +labels: assignees: '' --- ### Tasks -- [ ] Review [link to core release](xxx) +- [ ] Review [link to core release](xxx) - [ ] Tag frontend-monorepo - [ ] Create release and generate release notes - [ ] Run `@smoke` tests diff --git a/.github/workflows/add_issue_new_projects.yml b/.github/workflows/add_issue_new_projects.yml index b4ca89e1d..372656ad8 100644 --- a/.github/workflows/add_issue_new_projects.yml +++ b/.github/workflows/add_issue_new_projects.yml @@ -13,7 +13,7 @@ env: jobs: add_issue: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: 'Add issue to project board' run: | diff --git a/.github/workflows/ci-cd-trigger.yml b/.github/workflows/ci-cd-trigger.yml new file mode 100644 index 000000000..505127629 --- /dev/null +++ b/.github/workflows/ci-cd-trigger.yml @@ -0,0 +1,110 @@ +name: CI/CD + +on: + push: + branches: + - release/* + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review +jobs: + lint-test-build: + runs-on: ubuntu-22.04 + name: '(CI) lint + unit test + build' + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + # https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions + cache: yarn + + - name: Install root dependencies + run: yarn install --frozen-lockfile + + - name: Derive appropriate SHAs for base and head for `nx affected` commands + uses: nrwl/nx-set-shas@v3 + with: + main-branch-name: develop + + - name: Check formatting + run: yarn nx format:check + + - name: Lint affected + run: yarn nx affected:lint --max-warnings=0 + + - name: Build affected spec + run: yarn nx affected --target=build-spec + + - name: Test affected + run: yarn nx affected:test + + - name: Build affected + run: yarn nx affected:build + + # See affected apps + - name: See affected apps + run: | + echo ">>>> debug" + echo "NX Version: $nx_version" + echo "NX_BASE: ${{ env.NX_BASE }}" + echo "NX_HEAD: ${{ env.NX_HEAD }}" + echo ">>>> eof debug" + + affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)" + echo -n "Affected projects: $affected" + + projects_e2e="" + if [[ $affected == *"governance"* ]]; then projects_e2e+='"governance-e2e" '; fi + if [[ $affected == *"trading"* ]]; then projects_e2e+='"trading-e2e" '; fi + if [[ $affected == *"explorer"* ]]; then projects_e2e+='"explorer-e2e" '; fi + if [[ -z "$projects_e2e" ]]; then projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '; fi + projects_e2e=${projects_e2e%?} + projects_e2e=[${projects_e2e// /,}] + echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV + echo PROJECTS=$(echo $projects_e2e | sed 's|-e2e||g') >> $GITHUB_ENV + + outputs: + projects: ${{ env.PROJECTS }} + projects-e2e: ${{ env.PROJECTS_E2E }} + + cypress: + needs: lint-test-build + name: '(CI) cypress' + if: ${{ needs.lint-test-build.outputs.projects != '[]' }} + uses: ./.github/workflows/cypress-run.yml + secrets: inherit + with: + projects: ${{ needs.lint-test-build.outputs.projects-e2e }} + tags: '@smoke @regression' + + publish-dist: + needs: lint-test-build + name: '(CD) publish dist' + if: ${{ needs.lint-test-build.outputs.projects != '[]' }} + uses: ./.github/workflows/publish-dist.yml + secrets: inherit + with: + projects: ${{ needs.lint-test-build.outputs.projects }} + + # Report single result at the end, to avoid mess with required checks in PR + cypress-result: + if: ${{ always() }} + needs: cypress + runs-on: ubuntu-22.04 + steps: + - run: | + result="${{ needs.cypress.result }}" + if [[ $result == "success" || $result == "skipped" ]]; then + exit 0 + else + exit 1 + fi diff --git a/.github/workflows/cypress-live-test.yml b/.github/workflows/cypress-live-test.yml index 8b9426db1..13adf57d9 100644 --- a/.github/workflows/cypress-live-test.yml +++ b/.github/workflows/cypress-live-test.yml @@ -13,7 +13,7 @@ on: jobs: cypress-run: name: Run Cypress Trading tests -- live environment - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/cypress-run.yml b/.github/workflows/cypress-run.yml index 15f1f896e..15edc3967 100644 --- a/.github/workflows/cypress-run.yml +++ b/.github/workflows/cypress-run.yml @@ -1,4 +1,4 @@ -name: Cypress Run +name: (CI) Cypress Run on: workflow_call: inputs: diff --git a/.github/workflows/generate-queries.yml b/.github/workflows/generate-queries.yml index 9e874c0fb..43db3338c 100644 --- a/.github/workflows/generate-queries.yml +++ b/.github/workflows/generate-queries.yml @@ -8,22 +8,25 @@ on: jobs: master: name: Generate Queries - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 + + - name: Setup node + uses: actions/setup-node@v3 with: - fetch-depth: 0 - - name: Use Node.js 16 - id: Node - uses: actions/setup-node@v2 - with: - node-version: 16.15.1 + node-version-file: '.nvmrc' + # https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions + cache: yarn + - name: Install root dependencies - run: yarn install + run: yarn install --frozen-lockfile + - name: Generate queries run: node ./scripts/get-queries.js + - uses: actions/upload-artifact@v2 with: name: queries diff --git a/.github/workflows/lint_pr.yml b/.github/workflows/lint_pr.yml index 90a55a74b..06f7ca04d 100644 --- a/.github/workflows/lint_pr.yml +++ b/.github/workflows/lint_pr.yml @@ -3,21 +3,28 @@ name: Verify PR title on: pull_request: - types: [opened, ready_for_review, reopened, edited, synchronize] + types: + - opened + - ready_for_review + - reopened + - edited + - synchronize jobs: lint_pr: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 + + - name: Setup node + uses: actions/setup-node@v3 with: - fetch-depth: 0 - - name: Use Node.js 16 - id: Node - uses: actions/setup-node@v2 - with: - node-version: 16.15.1 + node-version-file: '.nvmrc' + # https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions + cache: yarn + - name: Install root dependencies - run: yarn install + run: yarn install --frozen-lockfile + - name: Check PR title run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js diff --git a/.github/workflows/pr-trigger.yml b/.github/workflows/pr-trigger.yml deleted file mode 100644 index b2a374248..000000000 --- a/.github/workflows/pr-trigger.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: PR Validations - -on: - push: - branches: - - develop - - main - pull_request: - types: - - opened - - reopened - - synchronize - - ready_for_review -jobs: - pr: - runs-on: ubuntu-latest - steps: - - name: Checkout frontend mono repo - uses: actions/checkout@v3 - with: - # We need to fetch all branches and commits so that Nx affected has a base to compare against. - fetch-depth: 0 - - - name: Check node version - id: node-version - run: | - npmVersion=$(cat .nvmrc | head -n 1) - echo ::set-output name=npmVersion::${npmVersion} - - - name: Setup node - uses: actions/setup-node@v3 - with: - node-version: ${{ steps.node-version.outputs.npmVersion }} - - # Check SHAs - - name: Derive appropriate SHAs for base and head for `nx affected` commands - uses: nrwl/nx-set-shas@v3 - with: - main-branch-name: develop - - # See affected apps - - name: See affected apps - run: | - nx_version=$(cat package.json | grep '"nx"' | cut -d ':' -f 2 | tr -d '",[:space:]') - rm package.json yarn.lock - yarn add nx@$nx_version - - echo ">>>> debug" - echo "NX Version: $nx_version" - echo "NX_BASE: ${{ env.NX_BASE }}" - echo "NX_HEAD: ${{ env.NX_HEAD }}" - - # echo "Main branch name: ${{ github.base_ref || github.ref_name }}" - # echo "git rev-parse HEAD: $(git rev-parse HEAD)" - # echo "Head: ${{ github.head_ref }}" - - # echo "command to execute: 'yarn nx print-affected --base=${{ github.base_ref || github.ref_name }} --head=${{ github.head_ref }} --select=projects'" - - # merge_base=$(git merge-base origin/develop HEAD) - # echo "git merge-base origin/develop HEAD: $merge_base" - - # head_sha="${{ github.event.pull_request.head.sha || github.sha }}" - # echo "Head SHA: $head_sha" - - # echo "command to execute (without nx-set-sha): 'yarn nx print-affected --base=$merge_base --head=$head_sha --select=projects'" - echo ">>>> eof debug" - - # affected_1=$(yarn nx print-affected --base=$merge_base --head=$head_sha --select=projects || true) - # echo -n "Affected projects (allowed to fail): $affected_1" - - # affected=$(yarn nx print-affected --base=${{ github.base_ref || github.ref_name }} --head=${{ github.head_ref }} --select=projects) - # echo -n "Affected projects: $affected" - - affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=HEAD --select=projects)" - echo -n "Affected projects: $affected" - - projects_e2e="" - if [[ $affected == *"governance"* ]]; then projects_e2e+='"governance-e2e" '; fi - if [[ $affected == *"trading"* ]]; then projects_e2e+='"trading-e2e" '; fi - if [[ $affected == *"explorer"* ]]; then projects_e2e+='"explorer-e2e" '; fi - if [[ -z "$projects_e2e" ]]; then projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '; fi - projects_e2e=${projects_e2e%?} - projects_e2e=[${projects_e2e// /,}] - echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV - echo PROJECTS=$(echo $projects_e2e | sed 's|-e2e||g') >> $GITHUB_ENV - - outputs: - projects: ${{ env.PROJECTS }} - projects-e2e: ${{ env.PROJECTS_E2E }} - - run-cypress: - needs: pr - if: ${{ needs.pr.outputs.projects != '[]' }} - uses: ./.github/workflows/cypress-run.yml - secrets: inherit - with: - projects: ${{ needs.pr.outputs.projects-e2e }} - tags: '@smoke @regression' - - run-docker-build: - needs: pr - if: ${{ needs.pr.outputs.projects != '[]' }} - uses: ./.github/workflows/publish-docker-containers.yml - secrets: inherit - with: - projects: ${{ needs.pr.outputs.projects }} - - # Report single result at the end, to avoid mess with required checks in PR - result: - if: ${{ always() }} - needs: run-cypress - runs-on: ubuntu-latest - name: Cypress result - steps: - - run: | - result="${{ needs.run-cypress.result }}" - if [[ $result == "success" || $result == "skipped" ]]; then - exit 0 - else - exit 1 - fi diff --git a/.github/workflows/process-tranches.yml b/.github/workflows/process-tranches.yml index 43723874b..eb5841cae 100644 --- a/.github/workflows/process-tranches.yml +++ b/.github/workflows/process-tranches.yml @@ -7,7 +7,7 @@ on: jobs: master: name: Generate Queries - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout diff --git a/.github/workflows/publish-docker-containers.yml b/.github/workflows/publish-dist.yml similarity index 57% rename from .github/workflows/publish-docker-containers.yml rename to .github/workflows/publish-dist.yml index 3797feaf6..6116c1b97 100644 --- a/.github/workflows/publish-docker-containers.yml +++ b/.github/workflows/publish-dist.yml @@ -1,4 +1,4 @@ -name: Docker build +name: (CD) Publish docker + s3 on: workflow_call: @@ -8,13 +8,13 @@ on: type: string jobs: - master: + publish-dist: strategy: fail-fast: false matrix: app: ${{ fromJSON(inputs.projects) }} name: ${{ matrix.app }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Check out code uses: actions/checkout@v3 @@ -29,41 +29,6 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - # https://docs.github.com/en/actions/learn-github-actions/contexts - # https://github.com/actions/checkout#Checkout-pull-request-HEAD-commit-instead-of-merge-commit - - name: Determine Docker Image tag - id: tags - run: | - npmVersion=$(cat .nvmrc | head -n 1) - versionTag=${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || github.event.pull_request.head.sha }} - echo ::set-output name=npmVersion::${npmVersion} - echo ::set-output name=version::${versionTag} - - - name: Print config - run: | - git rev-parse --verify HEAD - git status - echo "steps.tags.outputs.version=${{ steps.tags.outputs.version }}" - - - name: Build and export to local Docker - uses: docker/build-push-action@v3 - with: - load: true - build-args: | - APP=${{ matrix.app }} - NODE_VERSION=${{ steps.tags.outputs.npmVersion }} - tags: | - ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local - - - name: Sanity check docker image - run: | - echo "Check .env file" - docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat .env - echo "Check ipfs-hash" - docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat ipfs-hash - echo "List html directory" - docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local ls -lah - - name: Log in to the Container registry uses: docker/login-action@v2 with: @@ -71,17 +36,66 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push + # https://docs.github.com/en/actions/learn-github-actions/contexts + - name: Check node version + id: tags + run: | + nodeVersion=$(cat .nvmrc | head -n 1) + echo ::set-output name=nodeVersion::${nodeVersion} + + if [[ "${{ github.event_name }}" = "push" ]]; then + envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)" + bucketName="${{ github.event.repository.name }}-$envName" + echo ::set-output name=bucketName::${bucketName} + echo ::set-output name=envName::${envName} + fi + + - name: Build and export to local Docker id: docker_build uses: docker/build-push-action@v3 + with: + load: true + build-args: | + APP=${{ matrix.app }} + NODE_VERSION=${{ steps.tags.outputs.nodeVersion }} + ENV_NAME=${{ steps.tags.outputs.envName || '' }} + tags: | + ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local + + - name: Sanity check docker image + run: | + echo "Check ipfs-hash" + docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat ipfs-hash + + echo "List html directory" + docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local ls -lah + + echo "Copy dist to local filesystem" + docker create --name=dist ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local + docker cp dist:/usr/share/nginx/html dist + + echo "Check local dist" + ls -al dist + + - name: Publish dist as docker image + uses: docker/build-push-action@v3 + if: ${{ github.event_name == 'pull_request' }} with: push: true build-args: | APP=${{ matrix.app }} - NODE_VERSION=${{ steps.tags.outputs.npmVersion }} + NODE_VERSION=${{ steps.tags.outputs.nodeVersion }} tags: | - ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:latest - ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ steps.tags.outputs.version }} + ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }} + + # - uses: shallwefootball/s3-upload-action@master + # if: ${{ github.event_name == 'push' }} + # name: Upload dist S3 + # with: + # aws_key_id: ${{ secrets.AWS_KEY_ID }} + # aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} + # aws_bucket: ${{ steps.tags.outputs.bucketName }} + # source_dir: 'dist' - name: Add preview label uses: actions-ecosystem/action-add-labels@v1 diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 690bbec8a..4d522c9b4 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -19,29 +19,27 @@ on: jobs: publish: name: Build & Publish - Tag - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: contents: 'read' actions: 'read' steps: - name: Checkout uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: User Node.js 16 - id: Node + + - name: Setup node uses: actions/setup-node@v3 with: - node-version: 16.15.1 - - name: Restore node_modules from cache - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: node_modules-${{ hashFiles('**/yarn.lock') }} + node-version-file: '.nvmrc' + # https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions + cache: yarn + - name: Install root dependencies run: yarn install --frozen-lockfile + - name: Build project run: yarn nx build ${{inputs.project}} + - name: Publish project to @vegaprotocol uses: JS-DevTools/npm-publish@v1 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 413dac9e1..000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Unit tests & build - -on: - push: - branches: - - develop - - main - pull_request: -jobs: - pr: - name: Test and lint - PR - runs-on: ubuntu-latest - permissions: - contents: 'read' - actions: 'read' - 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: ${{ github.base_ref }} - - name: Use Node.js 16 - id: Node - uses: actions/setup-node@v3 - with: - node-version: 16.15.1 - - name: Restore node_modules from cache - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: node_modules-${{ hashFiles('**/yarn.lock') }} - - name: Install root dependencies - run: yarn install --frozen-lockfile - - 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 - - name: Build affected spec - run: yarn nx affected --target=build-spec diff --git a/Dockerfile b/Dockerfile index 326e7958d..2fe2cb35a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ FROM --platform=amd64 node:${NODE_VERSION}-alpine3.16 as build WORKDIR /app # Argument to allow building of different apps ARG APP +ARG ENV_NAME="" RUN apk add --update --no-cache \ python3 \ make \ @@ -18,16 +19,10 @@ RUN sh ./docker-build.sh # if this fails you need to docker pull nginx:1.23-alpine and pin new SHA # this is to ensure that we run always same version of alpine to make sure ipfs is indempotent FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629 -ARG APP # configuration of system -RUN apk add --no-cache bash go-ipfs EXPOSE 80 -COPY entrypoint.sh /entrypoint.sh -CMD ["/entrypoint.sh"] - # Copy dist WORKDIR /usr/share/nginx/html COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist/apps/${APP} /usr/share/nginx/html -COPY ./apps/${APP}/.env .env -RUN ipfs init && echo "$(ipfs add -rQ .)" > ipfs-hash +RUN apk add --no-cache go-ipfs; ipfs init && echo "$(ipfs add -rQ .)" > ipfs-hash; apk del go-ipfs diff --git a/apps/explorer/.env b/apps/explorer/.env index dbdbcd3d4..00d6f58d1 100644 --- a/apps/explorer/.env +++ b/apps/explorer/.env @@ -1,7 +1,7 @@ NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml -NX_VEGA_NETWORKS='{"SANDBOX":"https://sandbox.explorer.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}' +NX_VEGA_NETWORKS='{"STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}' NX_VEGA_ENV=STAGNET3 NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz diff --git a/apps/explorer/.env.mirror b/apps/explorer/.env.mirror deleted file mode 100644 index 8de0e3663..000000000 --- a/apps/explorer/.env.mirror +++ /dev/null @@ -1,20 +0,0 @@ -# App configuration variables -NX_TENDERMINT_URL=https://tm.be.mainnet-mirror.vega.xyz -NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.xyz/websocket -NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml -NX_VEGA_ENV=MIRROR -NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/ -NX_ETHERSCAN_URL=https://sepolia.etherscan.io -NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz -NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json - -# App flags -NX_EXPLORER_ASSETS=1 -NX_EXPLORER_GENESIS=1 -NX_EXPLORER_GOVERNANCE=1 -NX_EXPLORER_NETWORK_PARAMETERS=1 -NX_EXPLORER_PARTIES=1 -NX_EXPLORER_VALIDATORS=1 -NX_EXPLORER_MARKETS=0 -NX_EXPLORER_ORACLES=0 -NX_EXPLORER_TXS_LIST=1 diff --git a/apps/explorer/.env.sandbox b/apps/explorer/.env.sandbox deleted file mode 100644 index 1db66f917..000000000 --- a/apps/explorer/.env.sandbox +++ /dev/null @@ -1,12 +0,0 @@ -# App configuration variables -NX_VEGA_ENV=SANDBOX -NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml -NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz -NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet -NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 -NX_ETHERSCAN_URL=https://sepolia.etherscan.io -NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz -NX_TENDERMINT_WEBSOCKET_URL=wss://be.sandbox.vega.xyz/websocket -NX_ETHERSCAN_URL=https://sepolia.etherscan.io -NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz -NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json \ No newline at end of file diff --git a/apps/governance-e2e/.env b/apps/governance-e2e/.env index 7463399fc..de1f22211 100644 --- a/apps/governance-e2e/.env +++ b/apps/governance-e2e/.env @@ -13,6 +13,8 @@ NX_LOCAL_PROVIDER_URL=http://localhost:8545/ NX_VEGA_WALLET_URL=http://localhost:1789 NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz +NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json +NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions #Test configuration variables CYPRESS_FAIRGROUND=false diff --git a/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts b/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts index 5fbab97b7..e0223ab64 100644 --- a/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts +++ b/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts @@ -56,7 +56,7 @@ describe( navigateTo(navigation.proposals); }); - // 3001-VOTE-055 + // 3001-VOTE-050 3001-VOTE-054 3001-VOTE-055 3002-PROP-019 it('Newly created raw proposal details - shows proposal title and full description', function () { createRawProposal(); cy.get('@rawProposal').then((rawProposal) => { diff --git a/apps/governance-e2e/src/integration/flow/proposal-enacted.cy.ts b/apps/governance-e2e/src/integration/flow/proposal-enacted.cy.ts index 3231319b6..ee0f5863b 100644 --- a/apps/governance-e2e/src/integration/flow/proposal-enacted.cy.ts +++ b/apps/governance-e2e/src/integration/flow/proposal-enacted.cy.ts @@ -128,7 +128,7 @@ context( .and('be.visible'); }); - // 3001-VOTE-048 3001-VOTE-049 + // 3001-VOTE-048 3001-VOTE-049 3001-VOTE-050 it('Able to fail proposal due to lack of participation', function () { const proposalTitle = 'Add New free form proposal with short enactment'; const proposalTx = createFreeFormProposalTxBody(); diff --git a/apps/governance-e2e/src/integration/flow/proposal-flow.cy.ts b/apps/governance-e2e/src/integration/flow/proposal-flow.cy.ts index 5adb87c87..0785b8249 100644 --- a/apps/governance-e2e/src/integration/flow/proposal-flow.cy.ts +++ b/apps/governance-e2e/src/integration/flow/proposal-flow.cy.ts @@ -19,6 +19,7 @@ import { waitForSpinner, navigateTo, navigation, + closeDialog, } from '../../support/common.functions'; import { clickOnValidatorFromList, @@ -41,7 +42,6 @@ const vegaWalletNameElement = '[data-testid="wallet-name"]'; const vegaWallet = '[data-testid="vega-wallet"]'; const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]'; const newProposalSubmitButton = '[data-testid="proposal-submit"]'; -const dialogCloseButton = '[data-testid="dialog-close"]'; const viewProposalButton = '[data-testid="view-proposal-btn"]'; const rawProposalData = '[data-testid="proposal-data"]'; const minVoteButton = '[data-testid="min-vote"]'; @@ -177,7 +177,7 @@ context( 'be.visible' ); cy.contains('Proposal rejected', proposalTimeout).should('be.visible'); - cy.get(dialogCloseButton).click(); + closeDialog(); waitForProposalSync(); navigateTo(navigation.proposals); cy.get(rejectProposalsLink).click(); @@ -214,7 +214,7 @@ context( enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8)); cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.get(feedbackError).should('have.text', errorMsg); - cy.get(dialogCloseButton).click(); + closeDialog(); }); // 3002-PROP-009 @@ -227,7 +227,7 @@ context( enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8)); cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.get(feedbackError).should('have.text', errorMsg); - cy.get(dialogCloseButton).click(); + closeDialog(); }); it('Unable to create a freeform proposal - when json parent section contains unexpected field', function () { @@ -251,7 +251,7 @@ context( cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.get(feedbackError).should('have.text', errorMsg); - cy.get(dialogCloseButton).click(); + closeDialog(); cy.get(rawProposalData) .invoke('val') .should('contain', "i shouldn't be here"); @@ -279,7 +279,7 @@ context( cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.get(feedbackError).should('have.text', errorMsg); - cy.get(dialogCloseButton).click(); + closeDialog(); }); // 1005-PROP-009 diff --git a/apps/governance-e2e/src/integration/flow/proposal-forms.cy.ts b/apps/governance-e2e/src/integration/flow/proposal-forms.cy.ts index 2f9df3041..dd7209a56 100644 --- a/apps/governance-e2e/src/integration/flow/proposal-forms.cy.ts +++ b/apps/governance-e2e/src/integration/flow/proposal-forms.cy.ts @@ -1,4 +1,5 @@ import { + closeDialog, navigateTo, navigation, waitForSpinner, @@ -39,7 +40,6 @@ const maxVoteDeadline = '[data-testid="max-vote"]'; const minValidationDeadline = '[data-testid="min-validation"]'; const minEnactDeadline = '[data-testid="min-enactment"]'; const maxEnactDeadline = '[data-testid="max-enactment"]'; -const dialogCloseButton = '[data-testid="dialog-close"]'; const inputError = '[data-testid="input-error-text"]'; const enactmentDeadlineError = '[data-testid="enactment-before-voting-deadline"]'; @@ -48,6 +48,7 @@ const feedbackError = '[data-testid="Error"]'; const viewProposalBtn = 'view-proposal-btn'; const liquidityVoteStatus = 'liquidity-votes-status'; const tokenVoteStatus = 'token-votes-status'; +const proposalTermsSection = 'proposal'; const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey'); const epochTimeout = Cypress.env('epochTimeout'); const proposalTimeout = { timeout: 14000 }; @@ -68,7 +69,6 @@ context( { tags: '@slow' }, function () { before('connect wallets and set approval limit', function () { - cy.createMarket(); cy.visit('/'); vegaWalletSetSpecifiedApprovalAmount('1000'); }); @@ -78,6 +78,7 @@ context( waitForSpinner(); cy.connectVegaWallet(); ethereumWalletConnect(); + cy.createMarket(); ensureSpecifiedUnstakedTokensAreAssociated('1'); navigateTo(navigation.proposals); }); @@ -194,7 +195,7 @@ context( 'have.text', 'Invalid params: proposal_submission.terms.closing_timestamp (cannot be after enactment time)' ); - cy.get(dialogCloseButton).click(); + closeDialog(); cy.get(minVoteDeadline).click(); cy.get(enactmentDeadlineError).should('not.exist'); }); @@ -286,7 +287,7 @@ context( ); }); - // 3001-VOTE-092 + // 3001-VOTE-092 3004-PMAC-001 it('Able to submit update market proposal and vote for proposal', function () { vegaWalletFaucetAssetsWithoutCheck( 'fUSDC', @@ -347,8 +348,9 @@ context( // 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001 it('Able to submit new asset proposal using min deadlines', function () { + const proposalTitle = 'Test new asset proposal'; goToMakeNewProposal(governanceProposalType.NEW_ASSET); - cy.get(newProposalTitle).type('Test new asset proposal'); + cy.get(newProposalTitle).type(proposalTitle); cy.get(newProposalDescription).type('E2E test for proposals'); cy.fixture('/proposals/new-asset').then((newAssetProposal) => { const newAssetPayload = JSON.stringify(newAssetProposal); @@ -367,7 +369,7 @@ context( cy.contains('Proposal waiting for node vote', proposalTimeout).should( 'be.visible' ); - cy.get(dialogCloseButton).click(); + closeDialog(); cy.get(newProposalSubmitButton).should('be.visible').click(); // cannot submit a proposal with ERC20 address already in use cy.contains('Proposal rejected', proposalTimeout).should('be.visible'); @@ -377,6 +379,17 @@ context( 'PROPOSAL_ERROR_ERC20_ADDRESS_ALREADY_IN_USE' ); }); + closeDialog(); + navigateTo(navigation.proposals); + cy.contains(proposalTitle) + .parentsUntil(proposalListItem) + .within(() => { + cy.getByTestId(viewProposalBtn).click(); + }); + cy.getByTestId(proposalTermsSection).within(() => { + cy.contains('USDT Coin').should('be.visible'); + cy.contains('USDT').should('be.visible'); + }); }); it('Unable to submit new asset proposal with missing/invalid fields', function () { @@ -415,9 +428,15 @@ context( getProposalInformationFromTable('Proposed enactment') // 3001-VOTE-044 .invoke('text') .should('not.be.empty'); + // 3001-VOTE-030 3001-VOTE-031 + cy.getByTestId(proposalTermsSection).within(() => { + cy.contains('UpdateAsset').should('be.visible'); + cy.contains('UpdateERC20').should('be.visible'); + cy.contains('"lifetimeLimit": "10"').should('be.visible'); + }); }); - it('Able to submit update asset proposal using max deadline', function () { + it.only('Able to submit update asset proposal using max deadline', function () { goToMakeNewProposal(governanceProposalType.UPDATE_ASSET); enterUpdateAssetProposalDetails(); cy.get(maxVoteDeadline).click(); diff --git a/apps/governance-e2e/src/integration/flow/token-association-flow.cy.ts b/apps/governance-e2e/src/integration/flow/token-association-flow.cy.ts index 5d81caec0..330e9a4d3 100644 --- a/apps/governance-e2e/src/integration/flow/token-association-flow.cy.ts +++ b/apps/governance-e2e/src/integration/flow/token-association-flow.cy.ts @@ -183,6 +183,7 @@ context( // 1004-ASSO-018 // 1004-ASSO-024 // 1004-ASSO-023 + // 1004-ASSO-032 stakingPageAssociateTokens('2', { type: 'contract', diff --git a/apps/governance-e2e/src/integration/view/home.cy.ts b/apps/governance-e2e/src/integration/view/home.cy.ts index 0cce3da85..248a765b4 100644 --- a/apps/governance-e2e/src/integration/view/home.cy.ts +++ b/apps/governance-e2e/src/integration/view/home.cy.ts @@ -6,159 +6,203 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () { cy.get('nav', { timeout: 10000 }).should('be.visible'); }); - describe('with wallets disconnected', function () { - describe('Links and buttons', function () { - it('should have link for proposal page', function () { - cy.getByTestId('home-proposals').within(() => { - cy.get('[href="/proposals"]') - .should('exist') - .and('have.text', 'Browse, vote, and propose'); - }); - }); - it('should show open or enacted proposals with proposal summary', function () { - cy.get('body').then(($body) => { - if (!$body.find('[data-testid="proposals-list-item"]').length) { - cy.createMarket(); - cy.reload(); - waitForSpinner(); - } - }); - cy.getByTestId('proposals-list-item') - .should('have.length.at.least', 1) - .first() - .within(() => { - cy.getByTestId('proposal-title') - .invoke('text') - .should('not.be.empty'); - cy.getByTestId('proposal-type') - .invoke('text') - .should('not.be.empty'); - cy.getByTestId('proposal-description') - .invoke('text') - .should('not.be.empty'); - cy.getByTestId('proposal-details') - .invoke('text') - .should('not.be.empty'); - cy.getByTestId('proposal-status') - .invoke('text') - .should('not.be.empty'); - cy.getByTestId('vote-details') - .invoke('text') - .should('not.be.empty'); - cy.getByTestId('view-proposal-btn').should('be.visible'); - }); - }); - it('should have external link for governance', function () { - cy.getByTestId('home-proposals').within(() => { - cy.getByTestId('external-link') - .should('have.attr', 'href') - .and('contain', 'https://vega.xyz/governance'); - }); - }); - it('should have link for validator page', function () { - cy.getByTestId('home-validators').within(() => { - cy.get('[href="/validators"]') - .first() - .should('exist') - .and('have.text', 'Browse, and stake'); - }); - }); - it('should have external link for validators', function () { - cy.getByTestId('home-validators').within(() => { - cy.getByTestId('external-link') - .should('have.attr', 'href') - .and( - 'contain', - 'https://community.vega.xyz/c/mainnet-validator-candidates' - ); - }); - }); - it('should have information on active nodes', function () { - cy.getByTestId('node-information') - .first() - .should('contain.text', '2') - .and('contain.text', 'active nodes'); - }); - it('should have information on consensus nodes', function () { - cy.getByTestId('node-information') - .last() - .should('contain.text', '2') - .and('contain.text', 'consensus nodes'); - }); - it('should contain link to specific validators', function () { - cy.getByTestId('validators') - .should('have.length', '2') - .each(($validator) => { - cy.wrap($validator).find('a').should('have.attr', 'href'); - }); - }); - it('should have link for rewards page', function () { - cy.getByTestId('home-rewards').within(() => { - cy.get('[href="/rewards"]') - .first() - .should('exist') - .and('have.text', 'See rewards'); - }); - }); - it('should have link for withdrawal page', function () { - cy.getByTestId('home-vega-token').within(() => { - cy.get('[href="/token/withdraw"]') - .first() - .should('exist') - .and('have.text', 'Manage tokens'); - }); + describe('Links and buttons', function () { + it('should have link for proposal page', function () { + cy.getByTestId('home-proposals').within(() => { + cy.get('[href="/proposals"]') + .should('exist') + .and('have.text', 'Browse, vote, and propose'); }); }); - describe('Mobile view - navigation bar', function () { - before('Change to mobile resolution', function () { - cy.viewport('iphone-xr'); - }); - - it('should have burger button', () => { - cy.getByTestId('button-menu-drawer').should('be.visible').click(); - cy.getByTestId('menu-drawer').should('be.visible'); - }); - - it('should have link for proposal page', function () { - cy.getByTestId('menu-drawer').within(() => { - cy.get('[href="/proposals"]') - .should('exist') - .and('have.text', 'Proposals'); + it('should display announcement banner', function () { + cy.getByTestId('app-announcement') + .should('be.visible') + .within(() => { + cy.getByTestId('external-link').should('exist'); }); + cy.getByTestId('app-announcement-close').should('be.visible').click(); + cy.getByTestId('app-announcement').should('not.exist'); + }); + + it('should show open or enacted proposals with proposal summary', function () { + cy.get('body').then(($body) => { + if (!$body.find('[data-testid="proposals-list-item"]').length) { + cy.createMarket(); + cy.reload(); + waitForSpinner(); + } }); - it('should have link for validator page', function () { - cy.getByTestId('menu-drawer').within(() => { - cy.get('[href="/validators"]') + cy.getByTestId('proposals-list-item') + .should('have.length.at.least', 1) + .first() + .within(() => { + cy.getByTestId('proposal-title') + .invoke('text') + .should('not.be.empty'); + cy.getByTestId('proposal-type').invoke('text').should('not.be.empty'); + cy.getByTestId('proposal-description') + .invoke('text') + .should('not.be.empty'); + cy.getByTestId('proposal-details') + .invoke('text') + .should('not.be.empty'); + cy.getByTestId('proposal-status') + .invoke('text') + .should('not.be.empty'); + cy.getByTestId('vote-details').invoke('text').should('not.be.empty'); + cy.getByTestId('view-proposal-btn').should('be.visible'); + }); + }); + + it('should have external link for governance', function () { + cy.getByTestId('home-proposals').within(() => { + cy.getByTestId('external-link') + .should('have.attr', 'href') + .and('contain', 'https://vega.xyz/governance'); + }); + }); + + it('should have link for validator page', function () { + cy.getByTestId('home-validators').within(() => { + cy.get('[href="/validators"]') + .first() + .should('exist') + .and('have.text', 'Browse, and stake'); + }); + }); + + it('should have external link for validators', function () { + cy.getByTestId('home-validators').within(() => { + cy.getByTestId('external-link') + .should('have.attr', 'href') + .and( + 'contain', + 'https://community.vega.xyz/c/mainnet-validator-candidates' + ); + }); + }); + + it('should have information on active nodes', function () { + cy.getByTestId('node-information') + .first() + .should('contain.text', '2') + .and('contain.text', 'active nodes'); + }); + + it('should have information on consensus nodes', function () { + cy.getByTestId('node-information') + .last() + .should('contain.text', '2') + .and('contain.text', 'consensus nodes'); + }); + + it('should contain link to specific validators', function () { + cy.getByTestId('validators') + .should('have.length', '2') + .each(($validator) => { + cy.wrap($validator).find('a').should('have.attr', 'href'); + }); + }); + + it('should have link for rewards page', function () { + cy.getByTestId('home-rewards').within(() => { + cy.get('[href="/rewards"]') + .first() + .should('exist') + .and('have.text', 'See rewards'); + }); + }); + + it('should have link for withdrawal page', function () { + cy.getByTestId('home-vega-token').within(() => { + cy.get('[href="/token/withdraw"]') + .first() + .should('exist') + .and('have.text', 'Manage tokens'); + }); + }); + + it('should display network data', function () { + cy.getByTestId('git-network-data') + .should('contain.text', 'Reading network data from') + .within(() => { + cy.get('span') .first() - .should('exist') - .and('have.text', 'Validators'); + .should('have.text', 'http://localhost:3028/query'); + cy.getByTestId('link').should('exist'); }); - }); + }); - it('should have link for rewards page', function () { - cy.getByTestId('menu-drawer').within(() => { - cy.get('[href="/rewards"]') - .first() - .should('exist') - .and('have.text', 'Rewards'); + it('should display eth data', function () { + cy.getByTestId('git-eth-data') + .should('contain.text', 'Reading Ethereum data from') + .within(() => { + cy.get('span').should('have.text', 'http://localhost:8545'); }); - }); - it('should have link for withdrawal page', function () { - cy.getByTestId('menu-drawer').within(() => { - cy.get('[href="/token/withdraw"]') - .first() - .should('exist') - .and('have.text', 'Withdraw'); - }); - }); + }); - after(function () { - cy.viewport( - Cypress.config('viewportWidth'), - Cypress.config('viewportHeight') - ); + it('should contain link for known issues on Github', function () { + cy.getByTestId('git-info').within(() => { + cy.contains('Known issues and feedback on') + .find('[data-testid="link"]') + .should( + 'have.attr', + 'href', + 'https://github.com/vegaprotocol/feedback/discussions' + ); }); }); }); + + describe('Mobile view - navigation bar', function () { + before('Change to mobile resolution', function () { + cy.viewport('iphone-xr'); + }); + + it('should have burger button', () => { + cy.getByTestId('button-menu-drawer').should('be.visible').click(); + cy.getByTestId('menu-drawer').should('be.visible'); + }); + + it('should have link for proposal page', function () { + cy.getByTestId('menu-drawer').within(() => { + cy.get('[href="/proposals"]') + .should('exist') + .and('have.text', 'Proposals'); + }); + }); + it('should have link for validator page', function () { + cy.getByTestId('menu-drawer').within(() => { + cy.get('[href="/validators"]') + .first() + .should('exist') + .and('have.text', 'Validators'); + }); + }); + + it('should have link for rewards page', function () { + cy.getByTestId('menu-drawer').within(() => { + cy.get('[href="/rewards"]') + .first() + .should('exist') + .and('have.text', 'Rewards'); + }); + }); + it('should have link for withdrawal page', function () { + cy.getByTestId('menu-drawer').within(() => { + cy.get('[href="/token/withdraw"]') + .first() + .should('exist') + .and('have.text', 'Withdraw'); + }); + }); + + after(function () { + cy.viewport( + Cypress.config('viewportWidth'), + Cypress.config('viewportHeight') + ); + }); + }); }); diff --git a/apps/governance-e2e/src/support/common.functions.ts b/apps/governance-e2e/src/support/common.functions.ts index 5cea725fc..bcbe7b5a1 100644 --- a/apps/governance-e2e/src/support/common.functions.ts +++ b/apps/governance-e2e/src/support/common.functions.ts @@ -84,3 +84,7 @@ export function verifyEthWalletAssociatedBalance(amount: string) { .parent(txTimeout) .should('contain', amount, txTimeout); } + +export function closeDialog() { + cy.getByTestId('dialog-close').click(); +} diff --git a/apps/governance-e2e/src/support/governance.functions.ts b/apps/governance-e2e/src/support/governance.functions.ts index 2109335fd..c77e394ff 100644 --- a/apps/governance-e2e/src/support/governance.functions.ts +++ b/apps/governance-e2e/src/support/governance.functions.ts @@ -1,4 +1,4 @@ -import { navigateTo, navigation } from './common.functions'; +import { closeDialog, navigateTo, navigation } from './common.functions'; import { ensureSpecifiedUnstakedTokensAreAssociated } from './staking.functions'; const newProposalButton = '[data-testid="new-proposal-link"]'; @@ -12,7 +12,6 @@ const voteButtons = '[data-testid="vote-buttons"]'; const dialogTitle = '[data-testid="dialog-title"]'; const proposalVoteDeadline = '[data-testid="proposal-vote-deadline"]'; const newProposalSubmitButton = '[data-testid="proposal-submit"]'; -const dialogCloseButton = '[data-testid="dialog-close"]'; const epochTimeout = Cypress.env('epochTimeout'); const proposalTimeout = { timeout: 14000 }; @@ -125,7 +124,7 @@ export function voteForProposal(vote: string) { 'have.text', 'Transaction complete' ); - cy.get(dialogCloseButton).click(); + closeDialog(); } export function waitForProposalSync() { @@ -176,7 +175,7 @@ export function waitForProposalSubmitted() { 'be.visible' ); cy.contains('Proposal submitted', proposalTimeout).should('be.visible'); - cy.get(dialogCloseButton).click(); + closeDialog(); } export function createRawProposal(proposerBalance?: string) { diff --git a/apps/governance-e2e/src/support/staking.functions.ts b/apps/governance-e2e/src/support/staking.functions.ts index c2fa2f4d1..1e08b5afa 100644 --- a/apps/governance-e2e/src/support/staking.functions.ts +++ b/apps/governance-e2e/src/support/staking.functions.ts @@ -1,3 +1,4 @@ +import { closeDialog } from './common.functions'; import { vegaWalletTeardown } from './wallet-teardown.functions'; const tokenAmountInputBox = '[data-testid="token-amount-input"]'; @@ -18,7 +19,6 @@ const stakeValidatorListTotalStake = 'total-stake'; const stakeValidatorListTotalShare = 'total-stake-share'; const stakeValidatorListName = '[col-id="validator"]'; const vegaKeySelector = '#vega-key-selector'; -const dialogCloseButton = '[data-testid="dialog-close"]'; const txTimeout = Cypress.env('txTimeout'); const epochTimeout = Cypress.env('epochTimeout'); @@ -54,7 +54,7 @@ export function stakingValidatorPageRemoveStake(stake: string) { .and('contain', `Remove ${stake} $VEGA tokens at the end of epoch`) .and('be.visible') .click(); - cy.get(dialogCloseButton).click(); + closeDialog(); } export function stakingPageAssociateTokens( diff --git a/apps/governance/.env.capsule b/apps/governance/.env.capsule index 37f49fcd2..eeb84ea5b 100644 --- a/apps/governance/.env.capsule +++ b/apps/governance/.env.capsule @@ -5,6 +5,7 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_FAIRGROUND=false NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}' NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions +NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_VEGA_CONFIG_URL='' NX_VEGA_URL=http://localhost:3028/query @@ -16,7 +17,6 @@ NX_VEGA_WALLET_URL=http://localhost:1789 NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet NX_DELEGATIONS_PAGINATION=50 NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz -NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json #Test configuration variables CYPRESS_FAIRGROUND=false diff --git a/apps/governance/.env.mirror b/apps/governance/.env.mirror deleted file mode 100644 index e9d02a7f0..000000000 --- a/apps/governance/.env.mirror +++ /dev/null @@ -1,12 +0,0 @@ -# App configuration variables -NX_VEGA_ENV=MIRROR -NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml -NX_VEGA_URL=https://api.n00.mainnet-mirror.vega.xyz/graphql -NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}' -NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 -NX_ETHERSCAN_URL=https://sepolia.etherscan.io -NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions -NX_VEGA_EXPLORER_URL=https://mirror.explorer.vega.xyz -NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet -NX_DELEGATIONS_PAGINATION=50 -NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json \ No newline at end of file diff --git a/apps/governance/.env.sandbox b/apps/governance/.env.sandbox deleted file mode 100644 index e83df1671..000000000 --- a/apps/governance/.env.sandbox +++ /dev/null @@ -1,9 +0,0 @@ -# App configuration variables -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://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml -NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz -NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet -NX_DELEGATIONS_PAGINATION=50 -NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json \ No newline at end of file diff --git a/apps/governance/src/config/env.ts b/apps/governance/src/config/env.ts index 1796bbb57..5fdbeb9d6 100644 --- a/apps/governance/src/config/env.ts +++ b/apps/governance/src/config/env.ts @@ -12,6 +12,7 @@ const TRUTHY = ['1', 'true']; interface VegaContracts { claimAddress: string; lockedAddress: string; + tokenVestingAddress?: string; } const customClaimAddress = process.env['NX_CUSTOM_CLAIM_ADDRESS'] as string; @@ -36,21 +37,16 @@ export const ContractAddresses: { claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error lockedAddress: '0x0', // TODO not deployed to this env }, - SANDBOX: { - claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error - lockedAddress: '0x0', // TODO not deployed to this env - }, TESTNET: { claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error lockedAddress: '0x0', // TODO not deployed to this env }, - MIRROR: { - claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error - lockedAddress: '0x0', // TODO not deployed to this env - }, VALIDATOR_TESTNET: { claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error lockedAddress: '0x0', // TODO not deployed to this env + // This is a fallback contract address for the validator testnet network which does not + // have a vesting contract address set and is therefore not in the ethereum config + tokenVestingAddress: '0xadFcb7f93a24F8743a8e548d74d2ecB373c92866', }, MAINNET: { claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3', diff --git a/apps/governance/src/contexts/contracts/contracts-provider.tsx b/apps/governance/src/contexts/contracts/contracts-provider.tsx index 4e2eb46a6..7737a4248 100644 --- a/apps/governance/src/contexts/contracts/contracts-provider.tsx +++ b/apps/governance/src/contexts/contracts/contracts-provider.tsx @@ -49,6 +49,13 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => { signer = provider.getSigner(); } + const tokenVestingAddress = + config.token_vesting_contract?.address || + ENV.addresses.tokenVestingAddress; + if (!tokenVestingAddress) { + throw new Error('No token vesting address found'); + } + if (provider && config) { const staking = new StakingBridge( config.staking_bridge_contract.address, @@ -63,7 +70,7 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => { signer || provider ), vesting: new TokenVesting( - config.token_vesting_contract.address, + tokenVestingAddress, signer || provider ), claim: new Claim(ENV.addresses.claimAddress, signer || provider), diff --git a/apps/governance/src/routes/token/token-details/token-details.tsx b/apps/governance/src/routes/token/token-details/token-details.tsx index 2170e1115..7472c48e1 100644 --- a/apps/governance/src/routes/token/token-details/token-details.tsx +++ b/apps/governance/src/routes/token/token-details/token-details.tsx @@ -14,6 +14,7 @@ import { TokenDetailsCirculating } from './token-details-circulating'; import { SplashLoader } from '../../../components/splash-loader'; import { useEthereumConfig } from '@vegaprotocol/web3'; import { useContracts } from '../../../contexts/contracts/contracts-context'; +import { ENV } from '../../../config'; export const TokenDetails = ({ totalSupply, @@ -49,6 +50,9 @@ export const TokenDetails = ({ ); } + const tokenVestingContractAddress = + config.token_vesting_contract?.address || ENV.addresses.tokenVestingAddress; + return (
@@ -65,18 +69,20 @@ export const TokenDetails = ({ {token.address} - - {t('Vesting contract').toUpperCase()} - - {config.token_vesting_contract.address} - - + {tokenVestingContractAddress && ( + + {t('Vesting contract').toUpperCase()} + + {tokenVestingContractAddress} + + + )} {t('Total supply').toUpperCase()} diff --git a/apps/multisig-signer/.env.sandbox b/apps/multisig-signer/.env.sandbox deleted file mode 100644 index 09c51a52b..000000000 --- a/apps/multisig-signer/.env.sandbox +++ /dev/null @@ -1,5 +0,0 @@ -NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml -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://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml diff --git a/apps/static/src/assets/mainnet-tranches.json b/apps/static/src/assets/mainnet-tranches.json index 737ad035c..02f490ba2 100644 --- a/apps/static/src/assets/mainnet-tranches.json +++ b/apps/static/src/assets/mainnet-tranches.json @@ -3,12 +3,134 @@ "tranche_id": 56, "tranche_start": "2023-04-20T00:00:00.000Z", "tranche_end": "2023-05-20T00:00:00.000Z", - "total_added": "0", + "total_added": "10724.25", "total_removed": "0", - "locked_amount": "0", - "deposits": [], + "locked_amount": "10724.25", + "deposits": [ + { + "amount": "50", + "user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7", + "tx": "0xc38d2e52912f15452c76b863dbfad88927b49435c284eaf6997be4cf13782fc0" + }, + { + "amount": "6001.25", + "user": "0x94097462EF7c43D0aC732E7B18f830096D95207C", + "tx": "0xde2c8eedaba7ef1c953a7ca5439a8d7900ff01d92bd49d9dff1c9cdeaaa38d6f" + }, + { + "amount": "2378.75", + "user": "0xd5EAB7ecE436D997139dbCd2c38bCE932B65a8ea", + "tx": "0x79d5279a108f5621488b5fba2e0b4c1508fff80bd3cf7bd36f8d79f5a87f9fea" + }, + { + "amount": "1121.25", + "user": "0xf96afaB11e5617560b9183766470B23F45c38C56", + "tx": "0xd75c7996c5a9484ad3432eab458341b21eb76e117d68fb2885170bfb7b017da1" + }, + { + "amount": "1121.25", + "user": "0xf915Da10e5136352Ba049acB0545Deb119054256", + "tx": "0x6840e2b579765303fc31a04f43fcea1d5ccf157b46bbd1e3f3e7f6a2e6cea039" + }, + { + "amount": "51.75", + "user": "0x237D23FcA6d7B2530C7614a9cB921CF27924911E", + "tx": "0x3e9c304b5ca74d1238cc4ee2f1396812aac6e62eb8674e8be3e01c9ad3e5bcb0" + } + ], "withdrawals": [], - "users": [] + "users": [ + { + "address": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7", + "deposits": [ + { + "amount": "50", + "user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7", + "tranche_id": 56, + "tx": "0xc38d2e52912f15452c76b863dbfad88927b49435c284eaf6997be4cf13782fc0" + } + ], + "withdrawals": [], + "total_tokens": "50", + "withdrawn_tokens": "0", + "remaining_tokens": "50" + }, + { + "address": "0x94097462EF7c43D0aC732E7B18f830096D95207C", + "deposits": [ + { + "amount": "6001.25", + "user": "0x94097462EF7c43D0aC732E7B18f830096D95207C", + "tranche_id": 56, + "tx": "0xde2c8eedaba7ef1c953a7ca5439a8d7900ff01d92bd49d9dff1c9cdeaaa38d6f" + } + ], + "withdrawals": [], + "total_tokens": "6001.25", + "withdrawn_tokens": "0", + "remaining_tokens": "6001.25" + }, + { + "address": "0xd5EAB7ecE436D997139dbCd2c38bCE932B65a8ea", + "deposits": [ + { + "amount": "2378.75", + "user": "0xd5EAB7ecE436D997139dbCd2c38bCE932B65a8ea", + "tranche_id": 56, + "tx": "0x79d5279a108f5621488b5fba2e0b4c1508fff80bd3cf7bd36f8d79f5a87f9fea" + } + ], + "withdrawals": [], + "total_tokens": "2378.75", + "withdrawn_tokens": "0", + "remaining_tokens": "2378.75" + }, + { + "address": "0xf96afaB11e5617560b9183766470B23F45c38C56", + "deposits": [ + { + "amount": "1121.25", + "user": "0xf96afaB11e5617560b9183766470B23F45c38C56", + "tranche_id": 56, + "tx": "0xd75c7996c5a9484ad3432eab458341b21eb76e117d68fb2885170bfb7b017da1" + } + ], + "withdrawals": [], + "total_tokens": "1121.25", + "withdrawn_tokens": "0", + "remaining_tokens": "1121.25" + }, + { + "address": "0xf915Da10e5136352Ba049acB0545Deb119054256", + "deposits": [ + { + "amount": "1121.25", + "user": "0xf915Da10e5136352Ba049acB0545Deb119054256", + "tranche_id": 56, + "tx": "0x6840e2b579765303fc31a04f43fcea1d5ccf157b46bbd1e3f3e7f6a2e6cea039" + } + ], + "withdrawals": [], + "total_tokens": "1121.25", + "withdrawn_tokens": "0", + "remaining_tokens": "1121.25" + }, + { + "address": "0x237D23FcA6d7B2530C7614a9cB921CF27924911E", + "deposits": [ + { + "amount": "51.75", + "user": "0x237D23FcA6d7B2530C7614a9cB921CF27924911E", + "tranche_id": 56, + "tx": "0x3e9c304b5ca74d1238cc4ee2f1396812aac6e62eb8674e8be3e01c9ad3e5bcb0" + } + ], + "withdrawals": [], + "total_tokens": "51.75", + "withdrawn_tokens": "0", + "remaining_tokens": "51.75" + } + ] }, { "tranche_id": 55, @@ -67,9 +189,9 @@ "tranche_id": 54, "tranche_start": "2023-04-06T00:00:00.000Z", "tranche_end": "2023-05-06T00:00:00.000Z", - "total_added": "2970", + "total_added": "8565", "total_removed": "0", - "locked_amount": "2970", + "locked_amount": "8565", "deposits": [ { "amount": "33", @@ -86,6 +208,21 @@ "user": "0xE9F41a0090fcc7eaf626037003AAD44B17098E7C", "tx": "0x09d3e968eb8dbd6851f47b36f7bb9e7d25a078c5c1b23ce5341a66ce19578e65" }, + { + "amount": "678", + "user": "0x94097462EF7c43D0aC732E7B18f830096D95207C", + "tx": "0x157b13e78d027a5b6cf35fdae80a8cec7797149f35c23eb43faa07d74a03d70d" + }, + { + "amount": "2394", + "user": "0xd5EAB7ecE436D997139dbCd2c38bCE932B65a8ea", + "tx": "0xbe03bd7e16faa3586fe66ddaac2bcacba4604b175721eb10aee29b40248759ea" + }, + { + "amount": "2523", + "user": "0xf96afaB11e5617560b9183766470B23F45c38C56", + "tx": "0x49ab3f37bda02347cb1966e8fa21885a463f09d0593a890e96b2e36dfb8dec49" + }, { "amount": "111", "user": "0x268070d5EEd5b24E34a5F4C17B5482178b18089D", @@ -214,6 +351,51 @@ "withdrawn_tokens": "0", "remaining_tokens": "288" }, + { + "address": "0x94097462EF7c43D0aC732E7B18f830096D95207C", + "deposits": [ + { + "amount": "678", + "user": "0x94097462EF7c43D0aC732E7B18f830096D95207C", + "tranche_id": 54, + "tx": "0x157b13e78d027a5b6cf35fdae80a8cec7797149f35c23eb43faa07d74a03d70d" + } + ], + "withdrawals": [], + "total_tokens": "678", + "withdrawn_tokens": "0", + "remaining_tokens": "678" + }, + { + "address": "0xd5EAB7ecE436D997139dbCd2c38bCE932B65a8ea", + "deposits": [ + { + "amount": "2394", + "user": "0xd5EAB7ecE436D997139dbCd2c38bCE932B65a8ea", + "tranche_id": 54, + "tx": "0xbe03bd7e16faa3586fe66ddaac2bcacba4604b175721eb10aee29b40248759ea" + } + ], + "withdrawals": [], + "total_tokens": "2394", + "withdrawn_tokens": "0", + "remaining_tokens": "2394" + }, + { + "address": "0xf96afaB11e5617560b9183766470B23F45c38C56", + "deposits": [ + { + "amount": "2523", + "user": "0xf96afaB11e5617560b9183766470B23F45c38C56", + "tranche_id": 54, + "tx": "0x49ab3f37bda02347cb1966e8fa21885a463f09d0593a890e96b2e36dfb8dec49" + } + ], + "withdrawals": [], + "total_tokens": "2523", + "withdrawn_tokens": "0", + "remaining_tokens": "2523" + }, { "address": "0x268070d5EEd5b24E34a5F4C17B5482178b18089D", "deposits": [ @@ -561,7 +743,7 @@ "tranche_end": "2023-04-06T00:00:00.000Z", "total_added": "14099", "total_removed": "165.10784124668", - "locked_amount": "3067.69057198327407086", + "locked_amount": "1020.23509744623685628", "deposits": [ { "amount": "30", @@ -3349,7 +3531,7 @@ "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "86666.297", "total_removed": "0", - "locked_amount": "59299.9407029960677967199", + "locked_amount": "58231.0206471161547092772", "deposits": [ { "amount": "86666.297", @@ -3415,7 +3597,7 @@ "tranche_end": "2023-06-01T00:00:00.000Z", "total_added": "2500", "total_removed": "0", - "locked_amount": "861.882504070004", + "locked_amount": "800.044356684981575", "deposits": [ { "amount": "2500", @@ -3536,7 +3718,7 @@ "tranche_end": "2023-09-01T00:00:00.000Z", "total_added": "17500", "total_removed": "0", - "locked_amount": "14717.5995118760055", + "locked_amount": "14289.43755661231775", "deposits": [ { "amount": "12500", @@ -3802,8 +3984,8 @@ "tranche_start": "2023-02-01T00:00:00.000Z", "tranche_end": "2023-08-01T00:00:00.000Z", "total_added": "37500", - "total_removed": "11535.201330975", - "locked_amount": "25637.78583486801375", + "total_removed": "11781.2245815", + "locked_amount": "24705.08891574585375", "deposits": [ { "amount": "7500", @@ -3832,6 +4014,16 @@ "user": "0x0B4e6fcE839B01ef43DA6F890FAC7B1Afb004600", "tx": "0x3f63318adc1797232c2578fa0f5eaccd21318427a3c243490817f778678bb6c6" }, + { + "amount": "133.120395975", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0xf440dd4b160a155a08a3cc6e16706d5e4f9609bddd46c495217c377ef538c76f" + }, + { + "amount": "112.90285455", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0x69a4b32bad0a702a9e19ec1a77e4bc75d936f323720ca4c6213f2e4f41a750ed" + }, { "amount": "92.587476975", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", @@ -3952,6 +4144,18 @@ "tranche_id": 34, "tx": "0xe021c767a9587b28a6772b57666d72122e5d869c477596e246a4365ee6b7ce43" }, + { + "amount": "133.120395975", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 34, + "tx": "0xf440dd4b160a155a08a3cc6e16706d5e4f9609bddd46c495217c377ef538c76f" + }, + { + "amount": "112.90285455", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 34, + "tx": "0x69a4b32bad0a702a9e19ec1a77e4bc75d936f323720ca4c6213f2e4f41a750ed" + }, { "amount": "92.587476975", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", @@ -4062,8 +4266,8 @@ } ], "total_tokens": "7500", - "withdrawn_tokens": "2299.752052575", - "remaining_tokens": "5200.247947425" + "withdrawn_tokens": "2545.7753031", + "remaining_tokens": "4954.2246969" }, { "address": "0x0B4e6fcE839B01ef43DA6F890FAC7B1Afb004600", @@ -4101,7 +4305,7 @@ "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "129999.45", "total_removed": "0", - "locked_amount": "59245.836968509227749328", + "locked_amount": "58177.89216768362770314", "deposits": [ { "amount": "129999.45", @@ -4134,7 +4338,7 @@ "tranche_end": "2024-04-01T00:00:00.000Z", "total_added": "54144.7663", "total_removed": "0", - "locked_amount": "54144.7663", + "locked_amount": "53736.93921662980913205788", "deposits": [ { "amount": "54144.7663", @@ -4167,7 +4371,7 @@ "tranche_end": "2023-09-03T00:00:00.000Z", "total_added": "62600", "total_removed": "0", - "locked_amount": "26882.8490360223239", + "locked_amount": "26110.75656392694224", "deposits": [ { "amount": "10000", @@ -4360,7 +4564,7 @@ "tranche_end": "2023-09-17T00:00:00.000Z", "total_added": "5000", "total_removed": "0", - "locked_amount": "2338.973236935566", + "locked_amount": "2277.30450913242", "deposits": [ { "amount": "5000", @@ -4571,7 +4775,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "97499.58", "total_removed": "0", - "locked_amount": "1336.84868966454578257644", + "locked_amount": "289.2943236127702715019", "deposits": [ { "amount": "97499.58", @@ -4603,8 +4807,8 @@ "tranche_start": "2022-02-04T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "135173.4239508", - "total_removed": "98230.390980249184455396", - "locked_amount": "1827.2413614734815961140086816", + "total_removed": "133818.54965292963650862", + "locked_amount": "395.4153957972609424964042112", "deposits": [ { "amount": "135173.4239508", @@ -4613,6 +4817,11 @@ } ], "withdrawals": [ + { + "amount": "35588.158672680452053224", + "user": "0xc90eA4d8D214D548221EE3622a8BE1D61f7077A2", + "tx": "0xb5e4c868526bc95258404cf26ce9d4dc8522e3a6352ebf503e3808023b45c90d" + }, { "amount": "98230.390980249184455396", "user": "0xc90eA4d8D214D548221EE3622a8BE1D61f7077A2", @@ -4631,6 +4840,12 @@ } ], "withdrawals": [ + { + "amount": "35588.158672680452053224", + "user": "0xc90eA4d8D214D548221EE3622a8BE1D61f7077A2", + "tranche_id": 26, + "tx": "0xb5e4c868526bc95258404cf26ce9d4dc8522e3a6352ebf503e3808023b45c90d" + }, { "amount": "98230.390980249184455396", "user": "0xc90eA4d8D214D548221EE3622a8BE1D61f7077A2", @@ -4639,8 +4854,8 @@ } ], "total_tokens": "135173.4239508", - "withdrawn_tokens": "98230.390980249184455396", - "remaining_tokens": "36943.032970550815544604" + "withdrawn_tokens": "133818.54965292963650862", + "remaining_tokens": "1354.87429787036349138" } ] }, @@ -4650,7 +4865,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "32499.86", "total_removed": "0", - "locked_amount": "562.38915759984502962888", + "locked_amount": "121.701126098142348042122", "deposits": [ { "amount": "32499.86", @@ -4683,7 +4898,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "10833.29", "total_removed": "0", - "locked_amount": "183.052213503540805372", + "locked_amount": "39.612535585171623792263", "deposits": [ { "amount": "10833.29", @@ -4716,7 +4931,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "22749.93", "total_removed": "4720.860935375", - "locked_amount": "684.29005804973844628731", + "locked_amount": "148.080505317408011860722", "deposits": [ { "amount": "6500", @@ -4867,8 +5082,8 @@ "tranche_start": "2022-11-01T00:00:00.000Z", "tranche_end": "2023-05-01T00:00:00.000Z", "total_added": "22500", - "total_removed": "6111.900993675", - "locked_amount": "3946.20741252302085", + "total_removed": "6357.929999175", + "locked_amount": "3386.5892610497226", "deposits": [ { "amount": "7500", @@ -4892,6 +5107,21 @@ "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", "tx": "0x2ab0e85bffb0894e741265c4efb962d5f09831002daa4acc92ab87f72917bfe3" }, + { + "amount": "133.080110475", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0x0e47007cb85268a4e0c3c1143b96e4c98c9880060c4218babcadd0c82f8247a9" + }, + { + "amount": "112.943139975", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0xde2d6d5df9d5097adf8a894beb8a02f0bd1cc4da1eccc221f8bfa08403dfed87" + }, + { + "amount": "0.00575505", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0xa7a9a0e91b78fd7d8ba4c16bf5b58fb1a3cb19ec1302788e894f5e24ccb07904" + }, { "amount": "92.593232025", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", @@ -5067,6 +5297,24 @@ "tranche_id": 33, "tx": "0x2ab0e85bffb0894e741265c4efb962d5f09831002daa4acc92ab87f72917bfe3" }, + { + "amount": "133.080110475", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 33, + "tx": "0x0e47007cb85268a4e0c3c1143b96e4c98c9880060c4218babcadd0c82f8247a9" + }, + { + "amount": "112.943139975", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 33, + "tx": "0xde2d6d5df9d5097adf8a894beb8a02f0bd1cc4da1eccc221f8bfa08403dfed87" + }, + { + "amount": "0.00575505", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 33, + "tx": "0xa7a9a0e91b78fd7d8ba4c16bf5b58fb1a3cb19ec1302788e894f5e24ccb07904" + }, { "amount": "92.593232025", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", @@ -5249,8 +5497,8 @@ } ], "total_tokens": "7500", - "withdrawn_tokens": "6111.900993675", - "remaining_tokens": "1388.099006325" + "withdrawn_tokens": "6357.929999175", + "remaining_tokens": "1142.070000825" }, { "address": "0x2539b51EbDE65a75672aBcfE9439a706a99D18D1", @@ -5275,7 +5523,7 @@ "tranche_end": "2023-06-02T00:00:00.000Z", "total_added": "1939928.38", "total_removed": "928642.9598472029154", - "locked_amount": "338796.7791632851902753616", + "locked_amount": "314870.1961185216721358008", "deposits": [ { "amount": "1852091.69", @@ -8284,10 +8532,15 @@ "tranche_id": 10, "tranche_start": "2021-07-15T23:37:11.000Z", "tranche_end": "2021-07-15T23:37:11.000Z", - "total_added": "6359302.299000000000000001", - "total_removed": "6313483.280000000000000001", + "total_added": "6459302.299000000000000001", + "total_removed": "6413483.280000000000000001", "locked_amount": "0", "deposits": [ + { + "amount": "100000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tx": "0x8d1ae9c167843c9e98404d3db6231aa82dbfb942d41b6b9af46d44c2a5e194ab" + }, { "amount": "100000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -8820,6 +9073,11 @@ } ], "withdrawals": [ + { + "amount": "100000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tx": "0x8d1ae9c167843c9e98404d3db6231aa82dbfb942d41b6b9af46d44c2a5e194ab" + }, { "amount": "100000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -9295,6 +9553,12 @@ { "address": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", "deposits": [ + { + "amount": "100000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tranche_id": 10, + "tx": "0x8d1ae9c167843c9e98404d3db6231aa82dbfb942d41b6b9af46d44c2a5e194ab" + }, { "amount": "100000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -9489,6 +9753,12 @@ } ], "withdrawals": [ + { + "amount": "100000", + "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", + "tranche_id": 10, + "tx": "0x8d1ae9c167843c9e98404d3db6231aa82dbfb942d41b6b9af46d44c2a5e194ab" + }, { "amount": "100000", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", @@ -9676,8 +9946,8 @@ "tx": "0xac16a4ce688d40a482a59914d68c3a676592f8804ee8f0781b66a4ba5ccfbdfc" } ], - "total_tokens": "3156651", - "withdrawn_tokens": "3156651", + "total_tokens": "3256651", + "withdrawn_tokens": "3256651", "remaining_tokens": "0" }, { @@ -10741,7 +11011,7 @@ "tranche_id": 11, "tranche_start": "2021-09-03T00:00:00.000Z", "tranche_end": "2022-09-03T00:00:00.000Z", - "total_added": "57188.000000000000000003", + "total_added": "57213.000000000000000003", "total_removed": "48157.21518131551", "locked_amount": "0", "deposits": [ @@ -10760,6 +11030,11 @@ "user": "0xE9F41a0090fcc7eaf626037003AAD44B17098E7C", "tx": "0x971e9bcaace01278dc20ecd83d5de588d15be22c1797f4934cfd2ffb357a95ef" }, + { + "amount": "25", + "user": "0x363D3d06d70761c34D6EfEB25E409A9549E6970D", + "tx": "0x180cd3aca2d716aa233483a9a52b02e83d9ac88716ca85d998269965c7a01a96" + }, { "amount": "150", "user": "0xEb01fF124D71b6C7E6613fd6E0A86c28C733F008", @@ -22548,6 +22823,21 @@ "withdrawn_tokens": "0", "remaining_tokens": "472" }, + { + "address": "0x363D3d06d70761c34D6EfEB25E409A9549E6970D", + "deposits": [ + { + "amount": "25", + "user": "0x363D3d06d70761c34D6EfEB25E409A9549E6970D", + "tranche_id": 11, + "tx": "0x180cd3aca2d716aa233483a9a52b02e83d9ac88716ca85d998269965c7a01a96" + } + ], + "withdrawals": [], + "total_tokens": "25", + "withdrawn_tokens": "0", + "remaining_tokens": "25" + }, { "address": "0xEb01fF124D71b6C7E6613fd6E0A86c28C733F008", "deposits": [ @@ -38966,7 +39256,7 @@ "tranche_end": "2023-06-05T00:00:00.000Z", "total_added": "3732368.4671", "total_removed": "700133.348465855088393", - "locked_amount": "545114.0177930545133092967", + "locked_amount": "508347.190004695251441550046", "deposits": [ { "amount": "1998.95815", @@ -40347,8 +40637,8 @@ "tranche_start": "2022-06-05T00:00:00.000Z", "tranche_end": "2023-12-05T00:00:00.000Z", "total_added": "15870102.715470999700000001", - "total_removed": "812577.59329050683322952", - "locked_amount": "7232626.89306985865129106659442422357718304", + "total_removed": "814951.95458233668060452", + "locked_amount": "7102254.0823883096815429813321985808150852", "deposits": [ { "amount": "16249.93", @@ -40867,6 +41157,26 @@ "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", "tx": "0xe41c5aea235784efd3ce7c38abcaa73a915e4ad398fbd738c9cf76ee95487de4" }, + { + "amount": "499.227386873675625", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0x48e6b25d368a94770abfeffd5b28d7bb8b42269831394aa4b653c6eedd471909" + }, + { + "amount": "575.13946745617175", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0x83497ea22d041f3b818b20cc112250ea3e38dc24b89a0bc5fee7198d6399a9cc" + }, + { + "amount": "818.82253036937375", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0x58bac20de3636c17156dcb64322cc7612c48f553d941bc039c796ef4167eeeb5" + }, + { + "amount": "481.17190713062625", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tx": "0x625ce40117b67b756a12ad676cfbfd2c59e264b3c86adbc624bf7107c4dbc5d5" + }, { "amount": "545.4204142199725", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", @@ -42873,6 +43183,30 @@ "tranche_id": 2, "tx": "0xe41c5aea235784efd3ce7c38abcaa73a915e4ad398fbd738c9cf76ee95487de4" }, + { + "amount": "499.227386873675625", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0x48e6b25d368a94770abfeffd5b28d7bb8b42269831394aa4b653c6eedd471909" + }, + { + "amount": "575.13946745617175", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0x83497ea22d041f3b818b20cc112250ea3e38dc24b89a0bc5fee7198d6399a9cc" + }, + { + "amount": "818.82253036937375", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0x58bac20de3636c17156dcb64322cc7612c48f553d941bc039c796ef4167eeeb5" + }, + { + "amount": "481.17190713062625", + "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", + "tranche_id": 2, + "tx": "0x625ce40117b67b756a12ad676cfbfd2c59e264b3c86adbc624bf7107c4dbc5d5" + }, { "amount": "545.4204142199725", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", @@ -44249,8 +44583,8 @@ } ], "total_tokens": "259998.8875", - "withdrawn_tokens": "141121.612888745726875", - "remaining_tokens": "118877.274611254273125" + "withdrawn_tokens": "143495.97418057557425", + "remaining_tokens": "116502.91331942442575" }, { "address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c", @@ -46595,8 +46929,8 @@ "tranche_start": "2021-11-05T00:00:00.000Z", "tranche_end": "2023-05-05T00:00:00.000Z", "total_added": "14597706.0446472999", - "total_removed": "5790314.064810046934064816", - "locked_amount": "955669.740633044212659704752133556", + "total_removed": "5806817.570026167688715156", + "locked_amount": "835310.394250092778046317149087858", "deposits": [ { "amount": "129284.449", @@ -46830,6 +47164,46 @@ "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", "tx": "0xa1dcf42aa5cdb34107d55521fd3b28c8dcea0a6a991ba7ec370f091c2a97a610" }, + { + "amount": "4202.72107310637911484", + "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", + "tx": "0x55259c6fbd43ea2b1e081dc4964c6785ed4d03bb0cafc49de2f07685b056f487" + }, + { + "amount": "691.90098422602867775", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0xdf89247c6bdb472dcd750818d83cb70cbbe1ed660320ad1df6b1c94fb49f0161" + }, + { + "amount": "797.50404161762011275", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0xf7c393e9346520e22750d3065cb66f49e315599b24406aed2b9570b5a25babe0" + }, + { + "amount": "5343.35381683275644592", + "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", + "tx": "0x5a00c2a54f777e2d2ac0f487c6978d4c0edd8a2b146636e45ba94c6988a330f8" + }, + { + "amount": "4.83182614679928618", + "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", + "tx": "0x14811c8d77a39b0331cb2fdc96049b462916877050cc73143c494277304c2496" + }, + { + "amount": "1135.14149158400926325", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0xae9047ab72d73da1001c570a12dddc5e82fd523454c1b377928e008da9bdfa85" + }, + { + "amount": "3666.3885480971028369", + "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", + "tx": "0x54687b81b06562122894e34822a416021671c28f99066a23c5a69399b614409d" + }, + { + "amount": "661.66343451005891275", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tx": "0x523cbf99fb25ac9633e40a26cf1aa66ac5442392e577299159bf50e844a860e1" + }, { "amount": "755.482067130393772", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", @@ -49978,6 +50352,30 @@ "tranche_id": 3, "tx": "0xa1dcf42aa5cdb34107d55521fd3b28c8dcea0a6a991ba7ec370f091c2a97a610" }, + { + "amount": "691.90098422602867775", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0xdf89247c6bdb472dcd750818d83cb70cbbe1ed660320ad1df6b1c94fb49f0161" + }, + { + "amount": "797.50404161762011275", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0xf7c393e9346520e22750d3065cb66f49e315599b24406aed2b9570b5a25babe0" + }, + { + "amount": "1135.14149158400926325", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0xae9047ab72d73da1001c570a12dddc5e82fd523454c1b377928e008da9bdfa85" + }, + { + "amount": "661.66343451005891275", + "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", + "tranche_id": 3, + "tx": "0x523cbf99fb25ac9633e40a26cf1aa66ac5442392e577299159bf50e844a860e1" + }, { "amount": "755.482067130393772", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", @@ -52656,8 +53054,8 @@ } ], "total_tokens": "359123.469575", - "withdrawn_tokens": "335078.02995866618372525", - "remaining_tokens": "24045.43961633381627475" + "withdrawn_tokens": "338364.23991060390069175", + "remaining_tokens": "20759.22966439609930825" }, { "address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB", @@ -52899,6 +53297,30 @@ "tranche_id": 3, "tx": "0xbe429d608aa735a1bea0099ee9806e2b0a293262199fa9881a3672215e68a6c3" }, + { + "amount": "4202.72107310637911484", + "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", + "tranche_id": 3, + "tx": "0x55259c6fbd43ea2b1e081dc4964c6785ed4d03bb0cafc49de2f07685b056f487" + }, + { + "amount": "5343.35381683275644592", + "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", + "tranche_id": 3, + "tx": "0x5a00c2a54f777e2d2ac0f487c6978d4c0edd8a2b146636e45ba94c6988a330f8" + }, + { + "amount": "4.83182614679928618", + "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", + "tranche_id": 3, + "tx": "0x14811c8d77a39b0331cb2fdc96049b462916877050cc73143c494277304c2496" + }, + { + "amount": "3666.3885480971028369", + "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", + "tranche_id": 3, + "tx": "0x54687b81b06562122894e34822a416021671c28f99066a23c5a69399b614409d" + }, { "amount": "80421.5336580481619385", "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", @@ -53231,8 +53653,8 @@ } ], "total_tokens": "1266324.603486", - "withdrawn_tokens": "1179718.62954110669784558", - "remaining_tokens": "86605.97394489330215442" + "withdrawn_tokens": "1192935.92480528973552942", + "remaining_tokens": "73388.67868071026447058" }, { "address": "0xC5d9221EB9c28A69859264c0A2Fe0d3272228296", @@ -54061,7 +54483,7 @@ "tranche_end": "2023-04-05T00:00:00.000Z", "total_added": "5778205.3912159303", "total_removed": "3381706.364737906998719578", - "locked_amount": "60687.4908264066119772663325364137", + "locked_amount": "13132.78514323649783129733831977186", "deposits": [ { "amount": "552496.6455", @@ -56188,8 +56610,8 @@ "tranche_start": "2022-06-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z", "total_added": "472355.6199999996", - "total_removed": "38870.3290450237949", - "locked_amount": "86376.432123878662920840984880776", + "total_removed": "40152.1324861581949", + "locked_amount": "80550.51809266543339246000913244", "deposits": [ { "amount": "3000", @@ -62823,6 +63245,51 @@ "user": "0xdc040917f80aC350460e55467e4B6c52Fc5e220E", "tx": "0xd1ded90c18e54cae27c885ec52af8db6260e3eebfd23eb61092de4ad4d20c301" }, + { + "amount": "327.28491882", + "user": "0x872502542bbE5115004B85e230dfA7436245eBEd", + "tx": "0x40aee0670fcfcf9c5514679f7207a6701cd301ddc2ffa2d97af13b92d5e948ef" + }, + { + "amount": "0.001065448", + "user": "0x872502542bbE5115004B85e230dfA7436245eBEd", + "tx": "0xe3e87e4860455f3d794d59458dee4622fb4727c49290b747ddf791971d2c9066" + }, + { + "amount": "327.28796296", + "user": "0x47F67fF46d5F8Aad8Cc2D77C6Fd893d71350EebE", + "tx": "0x8ceeb54fd69be5d8887aad5bf4d628c274319ee3d9a88db939281108f548623e" + }, + { + "amount": "327.288571788", + "user": "0xB0473C4FeBd722a6d7eE561329d3134030318132", + "tx": "0xf41899ffea2bf68e78f747eb32043d983cf3803bd91ce550a70883f2b46f62b7" + }, + { + "amount": "28.223592084", + "user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83", + "tx": "0x4c003445f6506b21e28176229f41ae8dcb2d3feafda14d76081aa6b46996069b" + }, + { + "amount": "26.371004566", + "user": "0x96d882E908C06cD697Ea07266Fb8D14ae129A50b", + "tx": "0x93540a3930099c8b54157bb83ae50c8ced993009ae2091888f85704e6f13869c" + }, + { + "amount": "164.310191526", + "user": "0xfA580dabBa10A5Ef817C7dc26Aaa5fF579D45a9D", + "tx": "0xf1df34ff15a8c7238e188632cf69532edb92f0d8203bf3031e5d19338cb5a9ca" + }, + { + "amount": "58.7761643844", + "user": "0xED71B9A9b5633e9d31A0986693658CBbf23c3c1B", + "tx": "0xda5812fda239af9581d2d60c8f4b959e0a8cc5466e5422c5b8fa9df72435d3bc" + }, + { + "amount": "22.259969558", + "user": "0x28FC83947F02f59Cb36b40f97f2D32BBC5D00585", + "tx": "0x14da65fbf5ae2f3a593036daa58b3353ab88fc22666e5514f9160e252ad2fdf9" + }, { "amount": "154.812094114", "user": "0x27e2254A2A8c9c9D1321E5Fe64cAD88e7A4f0ba7", @@ -82069,6 +82536,12 @@ } ], "withdrawals": [ + { + "amount": "58.7761643844", + "user": "0xED71B9A9b5633e9d31A0986693658CBbf23c3c1B", + "tranche_id": 5, + "tx": "0xda5812fda239af9581d2d60c8f4b959e0a8cc5466e5422c5b8fa9df72435d3bc" + }, { "amount": "20.679657534", "user": "0xED71B9A9b5633e9d31A0986693658CBbf23c3c1B", @@ -82083,8 +82556,8 @@ } ], "total_tokens": "180", - "withdrawn_tokens": "89.6315011398", - "remaining_tokens": "90.3684988602" + "withdrawn_tokens": "148.4076655242", + "remaining_tokens": "31.5923344758" }, { "address": "0x68927b7A4A360f5B615e104BA2cA2E264a563A33", @@ -82798,10 +83271,17 @@ "tx": "0xc8541da6a57f410b6faba47a5e5184bae700193b7bd042914fffc562114d92f5" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "164.310191526", + "user": "0xfA580dabBa10A5Ef817C7dc26Aaa5fF579D45a9D", + "tranche_id": 5, + "tx": "0xf1df34ff15a8c7238e188632cf69532edb92f0d8203bf3031e5d19338cb5a9ca" + } + ], "total_tokens": "200", - "withdrawn_tokens": "0", - "remaining_tokens": "200" + "withdrawn_tokens": "164.310191526", + "remaining_tokens": "35.689808474" }, { "address": "0x40c9DF6b867cA219c0Ab4C2E390E8B919Be52e7e", @@ -84358,6 +84838,12 @@ } ], "withdrawals": [ + { + "amount": "22.259969558", + "user": "0x28FC83947F02f59Cb36b40f97f2D32BBC5D00585", + "tranche_id": 5, + "tx": "0x14da65fbf5ae2f3a593036daa58b3353ab88fc22666e5514f9160e252ad2fdf9" + }, { "amount": "17.060806698", "user": "0x28FC83947F02f59Cb36b40f97f2D32BBC5D00585", @@ -84390,8 +84876,8 @@ } ], "total_tokens": "200", - "withdrawn_tokens": "142.641089548", - "remaining_tokens": "57.358910452" + "withdrawn_tokens": "164.901059106", + "remaining_tokens": "35.098940894" }, { "address": "0xBef628af8547cE5264835F32487055a7e3dF393D", @@ -84919,6 +85405,12 @@ } ], "withdrawals": [ + { + "amount": "26.371004566", + "user": "0x96d882E908C06cD697Ea07266Fb8D14ae129A50b", + "tranche_id": 5, + "tx": "0x93540a3930099c8b54157bb83ae50c8ced993009ae2091888f85704e6f13869c" + }, { "amount": "57.900076104", "user": "0x96d882E908C06cD697Ea07266Fb8D14ae129A50b", @@ -84939,8 +85431,8 @@ } ], "total_tokens": "200", - "withdrawn_tokens": "137.635305682", - "remaining_tokens": "62.364694318" + "withdrawn_tokens": "164.006310248", + "remaining_tokens": "35.993689752" }, { "address": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83", @@ -84953,6 +85445,12 @@ } ], "withdrawals": [ + { + "amount": "28.223592084", + "user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83", + "tranche_id": 5, + "tx": "0x4c003445f6506b21e28176229f41ae8dcb2d3feafda14d76081aa6b46996069b" + }, { "amount": "41.084474888", "user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83", @@ -85015,8 +85513,8 @@ } ], "total_tokens": "400", - "withdrawn_tokens": "299.769393708", - "remaining_tokens": "100.230606292" + "withdrawn_tokens": "327.992985792", + "remaining_tokens": "72.007014208" }, { "address": "0xF5037DDA4A660d67560200f45380FF8364e35540", @@ -85305,10 +85803,17 @@ "tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "327.288571788", + "user": "0xB0473C4FeBd722a6d7eE561329d3134030318132", + "tranche_id": 5, + "tx": "0xf41899ffea2bf68e78f747eb32043d983cf3803bd91ce550a70883f2b46f62b7" + } + ], "total_tokens": "400", - "withdrawn_tokens": "0", - "remaining_tokens": "400" + "withdrawn_tokens": "327.288571788", + "remaining_tokens": "72.711428212" }, { "address": "0x8aBD57F15cB5f8BD8949EE90c67f77B680caB645", @@ -85335,10 +85840,17 @@ "tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "327.28796296", + "user": "0x47F67fF46d5F8Aad8Cc2D77C6Fd893d71350EebE", + "tranche_id": 5, + "tx": "0x8ceeb54fd69be5d8887aad5bf4d628c274319ee3d9a88db939281108f548623e" + } + ], "total_tokens": "400", - "withdrawn_tokens": "0", - "remaining_tokens": "400" + "withdrawn_tokens": "327.28796296", + "remaining_tokens": "72.71203704" }, { "address": "0x872502542bbE5115004B85e230dfA7436245eBEd", @@ -85350,10 +85862,23 @@ "tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8" } ], - "withdrawals": [], + "withdrawals": [ + { + "amount": "327.28491882", + "user": "0x872502542bbE5115004B85e230dfA7436245eBEd", + "tranche_id": 5, + "tx": "0x40aee0670fcfcf9c5514679f7207a6701cd301ddc2ffa2d97af13b92d5e948ef" + }, + { + "amount": "0.001065448", + "user": "0x872502542bbE5115004B85e230dfA7436245eBEd", + "tranche_id": 5, + "tx": "0xe3e87e4860455f3d794d59458dee4622fb4727c49290b747ddf791971d2c9066" + } + ], "total_tokens": "400", - "withdrawn_tokens": "0", - "remaining_tokens": "400" + "withdrawn_tokens": "327.285984268", + "remaining_tokens": "72.714015732" }, { "address": "0x3f8CEEE4f53d8FFBABbA13E3E90334c6702A8440", diff --git a/apps/static/src/assets/mirror-network.json b/apps/static/src/assets/mirror-network.json deleted file mode 100644 index 9de4de719..000000000 --- a/apps/static/src/assets/mirror-network.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "hosts": ["https://api-n00.mainnet-mirror.vega.rocks/graphql"] -} diff --git a/apps/static/src/assets/sandbox-network.json b/apps/static/src/assets/sandbox-network.json deleted file mode 100644 index 639f002ab..000000000 --- a/apps/static/src/assets/sandbox-network.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "hosts": ["https://api-n01.sandbox.vega.rocks/graphql"] -} diff --git a/apps/trading-e2e/.env b/apps/trading-e2e/.env index f9f44dd56..c50fe6d0b 100644 --- a/apps/trading-e2e/.env +++ b/apps/trading-e2e/.env @@ -19,6 +19,7 @@ CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545 CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint +CYPRESS_ORACLE_PUBKEY=6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61 CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65 CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535 CYPRESS_VEGA_ENV=CUSTOM diff --git a/apps/trading-e2e/src/integration/live-env.cy.ts b/apps/trading-e2e/src/integration/live-env.cy.ts index aa70fd337..5229dbd58 100644 --- a/apps/trading-e2e/src/integration/live-env.cy.ts +++ b/apps/trading-e2e/src/integration/live-env.cy.ts @@ -95,6 +95,16 @@ describe('Console - market info - live env', { tags: '@live' }, () => { cy.wrap(element).should('have.text', subtitles[index]); }); }); + + it('renders correctly liquidity in trading tab', () => { + cy.getByTestId('Liquidity').click(); + cy.contains('Loading').should('not.exist'); + cy.contains('Something went wrong').should('not.exist'); + cy.contains('Application error').should('not.exist'); + cy.getByTestId('tab-liquidity').within(() => { + cy.get('[col-id="party.id"]').eq(1).should('not.be.empty'); + }); + }); }); describe('Console - market summary - live env', { tags: '@live' }, () => { diff --git a/apps/trading-e2e/src/integration/market-info.cy.ts b/apps/trading-e2e/src/integration/market-info.cy.ts index 5a5463db5..185e10ed1 100644 --- a/apps/trading-e2e/src/integration/market-info.cy.ts +++ b/apps/trading-e2e/src/integration/market-info.cy.ts @@ -180,7 +180,15 @@ describe('market info is displayed', { tags: '@smoke' }, () => { 'termination.BTC.value' ); + // check that links to github for oracle proofs are shown cy.getByTestId(accordionContent) + .getByTestId('oracle-proof-links') + .find(`[data-testid="${externalLink}"]`) + .should('have.attr', 'href') + .and('contain', 'https://github.com/vegaprotocol/well-known'); + + cy.getByTestId(accordionContent) + .getByTestId('oracle-spec-links') .find(`[data-testid="${externalLink}"]`) .should('have.attr', 'href') .and('contain', '/oracles'); diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts index d3575cfbf..210b979ab 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts @@ -84,6 +84,8 @@ describe('must submit order', { tags: '@smoke' }, () => { type: Schema.OrderType.TYPE_MARKET, side: Schema.Side.SIDE_BUY, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK, + postOnly: false, + reduceOnly: false, size: '100', }; createOrder(order); @@ -98,6 +100,8 @@ describe('must submit order', { tags: '@smoke' }, () => { side: Schema.Side.SIDE_SELL, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC, size: '100', + postOnly: false, + reduceOnly: false, }; createOrder(order); testOrderSubmission(order); @@ -112,6 +116,8 @@ describe('must submit order', { tags: '@smoke' }, () => { side: Schema.Side.SIDE_BUY, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, size: '100', + postOnly: false, + reduceOnly: false, price: '200', }; createOrder(order); @@ -126,6 +132,8 @@ describe('must submit order', { tags: '@smoke' }, () => { side: Schema.Side.SIDE_SELL, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GFN, size: '100', + postOnly: false, + reduceOnly: false, price: '50000', }; createOrder(order); @@ -143,6 +151,8 @@ describe('must submit order', { tags: '@smoke' }, () => { size: '100', price: '1.00', expiresAt: expiresAt.toISOString().substring(0, 16), + postOnly: false, + reduceOnly: false, }; createOrder(order); @@ -150,6 +160,8 @@ describe('must submit order', { tags: '@smoke' }, () => { price: '100000', expiresAt: new Date(order.expiresAt as string).getTime().toString() + '000000', + postOnly: false, + reduceOnly: false, }); }); }); @@ -182,6 +194,8 @@ describe( side: Schema.Side.SIDE_BUY, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, size: '100', + postOnly: false, + reduceOnly: false, price: '200', }; createOrder(order); @@ -196,6 +210,8 @@ describe( side: Schema.Side.SIDE_SELL, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, size: '100', + postOnly: false, + reduceOnly: false, price: '50000', }; createOrder(order); @@ -210,12 +226,16 @@ describe( side: Schema.Side.SIDE_SELL, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT, size: '100', + postOnly: false, + reduceOnly: false, price: '1.00', expiresAt: displayTomorrow(), }; createOrder(order); testOrderSubmission(order, { price: '100000', + postOnly: false, + reduceOnly: false, expiresAt: new Date(order.expiresAt as string).getTime().toString() + '000000', }); @@ -251,6 +271,8 @@ describe( side: Schema.Side.SIDE_BUY, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, size: '100', + postOnly: false, + reduceOnly: false, price: '200', }; createOrder(order); @@ -265,6 +287,8 @@ describe( side: Schema.Side.SIDE_SELL, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, size: '100', + postOnly: false, + reduceOnly: false, price: '50000', }; createOrder(order); @@ -281,6 +305,8 @@ describe( size: '100', price: '1.00', expiresAt: displayTomorrow(), + postOnly: false, + reduceOnly: false, }; createOrder(order); testOrderSubmission(order, { @@ -320,6 +346,8 @@ describe( side: Schema.Side.SIDE_BUY, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, size: '100', + postOnly: false, + reduceOnly: false, price: '200', }; createOrder(order); @@ -335,6 +363,8 @@ describe( timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, size: '100', price: '50000', + postOnly: false, + reduceOnly: false, }; createOrder(order); testOrderSubmission(order, { price: '5000000000' }); @@ -350,6 +380,8 @@ describe( size: '100', price: '1.00', expiresAt: displayTomorrow(), + postOnly: false, + reduceOnly: false, }; createOrder(order); testOrderSubmission(order, { diff --git a/apps/trading-e2e/src/support/trading.ts b/apps/trading-e2e/src/support/trading.ts index 9f7998589..ccf1d9b63 100644 --- a/apps/trading-e2e/src/support/trading.ts +++ b/apps/trading-e2e/src/support/trading.ts @@ -35,6 +35,8 @@ type MarketPageMockData = { trigger?: Schema.AuctionTrigger; }; +const ORACLE_PUBKEY = Cypress.env('ORACLE_PUBKEY'); + const marketDataOverride = ( data: MarketPageMockData ): PartialDeep => ({ @@ -96,7 +98,54 @@ const mockTradingPage = ( aliasGQLQuery(req, 'Margins', marginsQuery()); aliasGQLQuery(req, 'Assets', assetsQuery()); aliasGQLQuery(req, 'Asset', assetQuery()); - aliasGQLQuery(req, 'MarketInfo', marketInfoQuery()); + aliasGQLQuery( + req, + 'MarketInfo', + marketInfoQuery({ + market: { + tradableInstrument: { + instrument: { + product: { + dataSourceSpecForSettlementData: { + data: { + sourceType: { + sourceType: { + signers: [ + { + __typename: 'Signer', + signer: { + __typename: 'PubKey', + key: ORACLE_PUBKEY, + }, + }, + ], + }, + }, + }, + }, + dataSourceSpecForTradingTermination: { + data: { + sourceType: { + sourceType: { + signers: [ + { + __typename: 'Signer', + signer: { + __typename: 'PubKey', + key: ORACLE_PUBKEY, + }, + }, + ], + }, + }, + }, + }, + }, + }, + }, + }, + }) + ); aliasGQLQuery(req, 'Trades', tradesQuery()); aliasGQLQuery(req, 'Chart', chartQuery()); aliasGQLQuery(req, 'Candles', candlesQuery()); @@ -127,6 +176,40 @@ export const addMockTradingPage = () => { cy.mockGQL((req) => { mockTradingPage(req, state, tradingMode, trigger); }); + + // Prevent request to github, return some dummy content + cy.intercept( + 'GET', + /^https:\/\/raw.githubusercontent.com\/vegaprotocol\/well-known/, + { + body: [ + { + name: 'Another oracle', + url: 'https://zombo.com', + description_markdown: + 'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n', + oracle: { + status: 'GOOD', + status_reason: '', + first_verified: '2022-01-01T00:00:00.000Z', + last_verified: '2022-12-31T00:00:00.000Z', + type: 'public_key', + public_key: ORACLE_PUBKEY, + }, + proofs: [ + { + format: 'signed_message', + available: true, + type: 'public_key', + public_key: ORACLE_PUBKEY, + message: 'SOMEHEX', + }, + ], + github_link: `https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/public_key-${ORACLE_PUBKEY}.toml`, + }, + ], + } + ); } ); }; diff --git a/apps/trading/.env b/apps/trading/.env index 86ab6f78a..c063c1979 100644 --- a/apps/trading/.env +++ b/apps/trading/.env @@ -2,6 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9 NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz +NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml NX_VEGA_ENV=STAGNET3 NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz diff --git a/apps/trading/client-pages/liquidity/liquidity.tsx b/apps/trading/client-pages/liquidity/liquidity.tsx index 8191493d8..64f53225e 100644 --- a/apps/trading/client-pages/liquidity/liquidity.tsx +++ b/apps/trading/client-pages/liquidity/liquidity.tsx @@ -1,4 +1,5 @@ import { + matchFilter, liquidityProvisionsDataProvider, LiquidityTable, lpAggregatedDataProvider, @@ -16,7 +17,6 @@ import { useNetworkParams, updateGridData, } from '@vegaprotocol/react-helpers'; -import * as Schema from '@vegaprotocol/types'; import { AsyncRenderer, Tab, @@ -25,19 +25,25 @@ import { Indicator, } from '@vegaprotocol/ui-toolkit'; import { useVegaWallet } from '@vegaprotocol/wallet'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { Header, HeaderStat, HeaderTitle } from '../../components/header'; import type { AgGridReact } from 'ag-grid-react'; import type { IGetRowsParams } from 'ag-grid-community'; -import type { LiquidityProvisionData } from '@vegaprotocol/liquidity'; +import type { LiquidityProvisionData, Filter } from '@vegaprotocol/liquidity'; import { Link, useParams } from 'react-router-dom'; import { Links, Routes } from '../../pages/client-router'; import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list'; +const enum LiquidityTabs { + Active = 'active', + Inactive = 'inactive', + MyLiquidityProvision = 'myLP', +} + export const Liquidity = () => { const params = useParams(); const marketId = params.marketId; @@ -48,18 +54,21 @@ const useReloadLiquidityData = (marketId: string | undefined) => { const { reload } = useDataProvider({ dataProvider: liquidityProvisionsDataProvider, variables: { marketId: marketId || '' }, + update: () => true, skip: !marketId, }); useEffect(() => { - const interval = setInterval(reload, 10000); + const interval = setInterval(reload, 30000); return () => clearInterval(interval); }, [reload]); }; export const LiquidityContainer = ({ marketId, + filter, }: { marketId: string | undefined; + filter?: Filter; }) => { const gridRef = useRef(null); const { data: market } = useMarket(marketId); @@ -78,7 +87,7 @@ export const LiquidityContainer = ({ const { data, loading, error } = useDataProvider({ dataProvider: lpAggregatedDataProvider, update, - variables: { marketId: marketId || '' }, + variables: { marketId: marketId || '', filter }, skip: !marketId, }); @@ -126,47 +135,9 @@ export const LiquidityContainer = ({ ); }; -export const LiquidityViewContainer = ({ - marketId, -}: { - marketId: string | undefined; -}) => { - const { pubKey } = useVegaWallet(); - const gridRef = useRef(null); +const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => { const { data: market } = useMarket(marketId); const { data: marketData } = useStaticMarketData(marketId); - - const dataRef = useRef(null); - - // To be removed when liquidityProvision subscriptions are working - useReloadLiquidityData(marketId); - - const update = useCallback( - ({ data }: { data: LiquidityProvisionData[] | null }) => { - if (!gridRef.current?.api) { - return false; - } - if (dataRef.current?.length) { - dataRef.current = data; - gridRef.current.api.refreshInfiniteCache(); - return true; - } - return false; - }, - [gridRef] - ); - - const { - data: liquidityProviders, - loading, - error, - } = useDataProvider({ - dataProvider: lpAggregatedDataProvider, - update, - variables: { marketId: marketId || '' }, - skip: !marketId, - }); - const targetStake = marketData?.targetStake; const suppliedStake = marketData?.suppliedStake; const assetDecimalPlaces = @@ -178,44 +149,8 @@ export const LiquidityViewContainer = ({ NetworkParams.market_liquidity_stakeToCcyVolume, NetworkParams.market_liquidity_targetstake_triggering_ratio, ]); - const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume; const triggeringRatio = params.market_liquidity_targetstake_triggering_ratio || '1'; - const myLpEdges = useMemo( - () => liquidityProviders?.filter((e) => e.party.id === pubKey), - [liquidityProviders, pubKey] - ); - const activeEdges = useMemo( - () => - liquidityProviders?.filter( - (e) => e.status === Schema.LiquidityProvisionStatus.STATUS_ACTIVE - ), - [liquidityProviders] - ); - const inactiveEdges = useMemo( - () => - liquidityProviders?.filter( - (e) => e.status !== Schema.LiquidityProvisionStatus.STATUS_ACTIVE - ), - [liquidityProviders] - ); - - const enum LiquidityTabs { - Active = 'active', - Inactive = 'inactive', - MyLiquidityProvision = 'myLP', - } - - const getActiveDefaultId = () => { - if (myLpEdges && myLpEdges.length > 0) { - return LiquidityTabs.MyLiquidityProvision; - } - if (activeEdges?.length) return LiquidityTabs.Active; - else if (inactiveEdges && inactiveEdges.length > 0) { - return LiquidityTabs.Inactive; - } - return LiquidityTabs.Active; - }; const { percentage, status } = useCheckLiquidityStatus({ suppliedStake: suppliedStake || 0, @@ -224,106 +159,113 @@ export const LiquidityViewContainer = ({ }); return ( - -
-
- {t('Go to trading')} - - } - /> - ) - } - > - -
- {targetStake - ? `${addDecimalsFormatNumber( - targetStake, - assetDecimalPlaces ?? 0 - )} ${symbol}` - : '-'} -
-
- -
- {suppliedStake - ? `${addDecimalsFormatNumber( - suppliedStake, - assetDecimalPlaces ?? 0 - )} ${symbol}` - : '-'} -
-
- - +
+ {t('Go to trading')} + + } + /> + ) + } + > + +
+ {targetStake + ? `${addDecimalsFormatNumber( + targetStake, + assetDecimalPlaces ?? 0 + )} ${symbol}` + : '-'} +
+
+ +
+ {suppliedStake + ? `${addDecimalsFormatNumber( + suppliedStake, + assetDecimalPlaces ?? 0 + )} ${symbol}` + : '-'} +
+
+ + - {formatNumberPercentage(percentage, 2)} - - -
{marketId}
-
-
- - - - {activeEdges && ( - - )} - - { - - {inactiveEdges && ( - - )} - - } - -
-
+ {formatNumberPercentage(percentage, 2)} + + +
{marketId}
+
+ + ); +}); +LiquidityViewHeader.displayName = 'LiquidityViewHeader'; + +export const LiquidityViewContainer = ({ + marketId, +}: { + marketId: string | undefined; +}) => { + const [tab, setTab] = useState(undefined); + const { pubKey } = useVegaWallet(); + + const { data } = useDataProvider({ + dataProvider: lpAggregatedDataProvider, + skipUpdates: true, + variables: { marketId: marketId || '' }, + skip: !marketId, + }); + + useEffect(() => { + if (data) { + if (pubKey && data.some((lp) => matchFilter({ partyId: pubKey }, lp))) { + setTab(LiquidityTabs.MyLiquidityProvision); + return; + } + if (data.some((lp) => matchFilter({ active: true }, lp))) { + setTab(LiquidityTabs.Active); + return; + } + setTab(LiquidityTabs.Inactive); + } + }, [data, pubKey]); + + return ( +
+ + + + + + + + + + +
); }; diff --git a/apps/trading/client-pages/market/market.tsx b/apps/trading/client-pages/market/market.tsx index 75bedefbd..a98849681 100644 --- a/apps/trading/client-pages/market/market.tsx +++ b/apps/trading/client-pages/market/market.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; import { @@ -13,6 +13,7 @@ import { useGlobalStore, usePageTitleStore } from '../../stores'; import { TradeGrid, TradePanels } from './trade-grid'; import { useNavigate, useParams } from 'react-router-dom'; import { Links, Routes } from '../../pages/client-router'; +import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; const calculatePrice = (markPrice?: string, decimalPlaces?: number) => { return markPrice && decimalPlaces @@ -66,14 +67,7 @@ export const MarketPage = () => { const update = useGlobalStore((store) => store.update); const lastMarketId = useGlobalStore((store) => store.marketId); - const onSelect = useCallback( - (id: string) => { - if (id && id !== marketId) { - navigate(Links[Routes.MARKET](id)); - } - }, - [marketId, navigate] - ); + const onSelect = useMarketClickHandler(); const { data, error, loading } = useDataProvider({ dataProvider: marketProvider, diff --git a/apps/trading/client-pages/market/trade-grid.tsx b/apps/trading/client-pages/market/trade-grid.tsx index f8383d1f0..580fb6518 100644 --- a/apps/trading/client-pages/market/trade-grid.tsx +++ b/apps/trading/client-pages/market/trade-grid.tsx @@ -8,7 +8,7 @@ import { TradesContainer } from '@vegaprotocol/trades'; import { LayoutPriority } from 'allotment'; import classNames from 'classnames'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { memo, useCallback, useState } from 'react'; +import { memo, useState } from 'react'; import type { ReactNode, ComponentProps } from 'react'; import { DepthChartContainer } from '@vegaprotocol/market-depth'; import { CandlesChartContainer } from '@vegaprotocol/candles-chart'; @@ -27,9 +27,9 @@ import { TradeMarketHeader } from './trade-market-header'; import { NO_MARKET } from './constants'; import { LiquidityContainer } from '../liquidity/liquidity'; import { useNavigate } from 'react-router-dom'; -import { Links, Routes } from '../../pages/client-router'; import type { PinnedAsset } from '@vegaprotocol/accounts'; import { useScreenDimensions } from '@vegaprotocol/react-helpers'; +import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; type MarketDependantView = | typeof CandlesChartContainer @@ -66,7 +66,7 @@ type TradingView = keyof typeof TradingViews; interface TradeGridProps { market: Market | null; - onSelect: (marketId: string) => void; + onSelect: (marketId: string, metaKey?: boolean) => void; pinnedAsset?: PinnedAsset; } @@ -78,15 +78,7 @@ interface BottomPanelProps { const MarketBottomPanel = memo( ({ marketId, pinnedAsset }: BottomPanelProps) => { const { screenSize } = useScreenDimensions(); - const navigate = useNavigate(); - const onMarketClick = useCallback( - (marketId: string) => { - navigate(Links[Routes.MARKET](marketId), { - replace: true, - }); - }, - [navigate] - ); + const onMarketClick = useMarketClickHandler(true); return 'xxxl' === screenSize ? ( @@ -189,7 +181,7 @@ const MainGrid = memo( pinnedAsset, }: { marketId: string; - onSelect?: (marketId: string) => void; + onSelect: (marketId: string, metaKey?: boolean) => void; pinnedAsset?: PinnedAsset; }) => { const navigate = useNavigate(); @@ -230,12 +222,7 @@ const MainGrid = memo( /> - { - onSelect?.(id); - }} - /> + @@ -304,7 +291,7 @@ const TradeGridChild = ({ children }: TradeGridChildProps) => { interface TradePanelsProps { market: Market | null; - onSelect: (marketId: string) => void; + onSelect: (marketId: string, metaKey?: boolean) => void; onMarketClick?: (marketId: string) => void; onClickCollateral: () => void; pinnedAsset?: PinnedAsset; @@ -320,7 +307,7 @@ export const TradePanels = ({ const renderView = () => { const Component = memo<{ marketId: string; - onSelect: (marketId: string) => void; + onSelect: (marketId: string, metaKey?: boolean) => void; onMarketClick?: (marketId: string) => void; onClickCollateral: () => void; pinnedAsset?: PinnedAsset; diff --git a/apps/trading/client-pages/market/trade-market-header.tsx b/apps/trading/client-pages/market/trade-market-header.tsx index c9e4c814b..96ebae4f3 100644 --- a/apps/trading/client-pages/market/trade-market-header.tsx +++ b/apps/trading/client-pages/market/trade-market-header.tsx @@ -22,7 +22,7 @@ import { MarketState as State } from '@vegaprotocol/types'; interface TradeMarketHeaderProps { market: Market | null; - onSelect: (marketId: string) => void; + onSelect: (marketId: string, metaKey?: boolean) => void; } export const TradeMarketHeader = ({ @@ -91,7 +91,6 @@ export const TradeMarketHeader = ({ diff --git a/apps/trading/client-pages/markets/markets.tsx b/apps/trading/client-pages/markets/markets.tsx index ffdf3eb94..7a172583c 100644 --- a/apps/trading/client-pages/markets/markets.tsx +++ b/apps/trading/client-pages/markets/markets.tsx @@ -1,16 +1,7 @@ -import { useCallback } from 'react'; import { MarketsContainer } from '@vegaprotocol/market-list'; -import { useNavigate } from 'react-router-dom'; -import { Links, Routes } from '../../pages/client-router'; +import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; export const Markets = () => { - const navigate = useNavigate(); - const handleOnSelect = useCallback( - (marketId: string) => { - navigate(Links[Routes.MARKET](marketId)); - }, - [navigate] - ); - + const handleOnSelect = useMarketClickHandler(); return ; }; diff --git a/apps/trading/client-pages/portfolio/portfolio.tsx b/apps/trading/client-pages/portfolio/portfolio.tsx index 099d4e0d1..a8e9bcf8c 100644 --- a/apps/trading/client-pages/portfolio/portfolio.tsx +++ b/apps/trading/client-pages/portfolio/portfolio.tsx @@ -19,25 +19,18 @@ import { usePageTitleStore } from '../../stores'; import { LedgerContainer } from '@vegaprotocol/ledger'; import { AccountsContainer } from '../../components/accounts-container'; import { AccountHistoryContainer } from './account-history-container'; -import { useNavigate } from 'react-router-dom'; -import { Links, Routes } from '../../pages/client-router'; +import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; export const Portfolio = () => { const { updateTitle } = usePageTitleStore((store) => ({ updateTitle: store.updateTitle, })); - const navigate = useNavigate(); - useEffect(() => { updateTitle(titlefy([t('Portfolio')])); }, [updateTitle]); - const onMarketClick = (marketId: string) => { - navigate(Links[Routes.MARKET](marketId), { - replace: true, - }); - }; + const onMarketClick = useMarketClickHandler(true); const wrapperClasses = 'h-full max-h-full flex flex-col'; return ( diff --git a/apps/trading/components/market-trading-mode/market-trading-mode.tsx b/apps/trading/components/market-trading-mode/market-trading-mode.tsx index c3c1a50e3..05be7298f 100644 --- a/apps/trading/components/market-trading-mode/market-trading-mode.tsx +++ b/apps/trading/components/market-trading-mode/market-trading-mode.tsx @@ -25,7 +25,7 @@ const getTradingModeLabel = ( interface HeaderStatMarketTradingModeProps { marketId?: string; - onSelect?: (marketId: string) => void; + onSelect?: (marketId: string, metaKey?: boolean) => void; initialTradingMode?: Schema.MarketTradingMode; initialTrigger?: Schema.AuctionTrigger; } @@ -66,7 +66,9 @@ export const MarketTradingMode = ({ return ( } + description={ + + } > {getTradingModeLabel( diff --git a/apps/trading/components/select-market/select-market-columns.tsx b/apps/trading/components/select-market/select-market-columns.tsx index 817992b94..78d241ece 100644 --- a/apps/trading/components/select-market/select-market-columns.tsx +++ b/apps/trading/components/select-market/select-market-columns.tsx @@ -1,4 +1,4 @@ -import type { RefObject } from 'react'; +import type { RefObject, MouseEvent } from 'react'; import { FeesCell } from '@vegaprotocol/market-info'; import { calcCandleHigh, @@ -157,14 +157,14 @@ export const columnHeaders: Column[] = [ ]; export type OnCellClickHandler = ( - e: React.MouseEvent, + e: MouseEvent, kind: ColumnKind, value: string ) => void; export const columns = ( market: MarketMaybeWithDataAndCandles, - onSelect: (id: string) => void, + onSelect: (id: string, metaKey?: boolean) => void, onCellClick: OnCellClickHandler, inViewRoot?: RefObject ) => { @@ -174,14 +174,7 @@ export const columns = ( const candleLow = market.candles && calcCandleLow(market.candles); const candleHigh = market.candles && calcCandleHigh(market.candles); const candleVolume = market.candles && calcCandleVolume(market.candles); - const handleKeyPress = ( - event: React.KeyboardEvent, - id: string - ) => { - if (event.key === 'Enter' && onSelect) { - return onSelect(id); - } - }; + const selectMarketColumns: Column[] = [ { kind: ColumnKind.Market, @@ -189,10 +182,10 @@ export const columns = ( handleKeyPress(event, market.id)} onClick={(e) => { e.preventDefault(); - onSelect(market.id); + e.stopPropagation(); + onSelect(market.id, e.metaKey); }} > {market.tradableInstrument.instrument.code} @@ -352,7 +345,7 @@ export const columns = ( export const columnsPositionMarkets = ( market: MarketMaybeWithDataAndCandles, - onSelect: (id: string) => void, + onSelect: (id: string, metaKey?: boolean) => void, inViewRoot?: RefObject, openVolume?: string, onCellClick?: OnCellClickHandler @@ -362,14 +355,6 @@ export const columnsPositionMarkets = ( .filter((c: string | undefined): c is CandleClose => !isNil(c)); const candleLow = market.candles && calcCandleLow(market.candles); const candleHigh = market.candles && calcCandleHigh(market.candles); - const handleKeyPress = ( - event: React.KeyboardEvent, - id: string - ) => { - if (event.key === 'Enter' && onSelect) { - return onSelect(id); - } - }; const candleVolume = market.candles && calcCandleVolume(market.candles); const selectMarketColumns: Column[] = [ { @@ -378,10 +363,10 @@ export const columnsPositionMarkets = ( handleKeyPress(event, market.id)} onClick={(e) => { e.preventDefault(); - onSelect(market.id); + e.stopPropagation(); + onSelect(market.id, e.metaKey); }} > {market.tradableInstrument.instrument.code} diff --git a/apps/trading/components/select-market/select-market-table.tsx b/apps/trading/components/select-market/select-market-table.tsx index 36a3e5539..4c3684d47 100644 --- a/apps/trading/components/select-market/select-market-table.tsx +++ b/apps/trading/components/select-market/select-market-table.tsx @@ -37,14 +37,14 @@ export const SelectMarketTableRow = ({ }: { detailed?: boolean; columns: Column[]; - onSelect: (id: string) => void; + onSelect: (id: string, metaKey?: boolean) => void; marketId: string; }) => { return ( { - onSelect(marketId); + onClick={(ev) => { + onSelect(marketId, ev.metaKey); }} data-testid={`market-link-${marketId}`} > diff --git a/apps/trading/components/select-market/select-market.spec.tsx b/apps/trading/components/select-market/select-market.spec.tsx index b87eb9938..e7fec9a86 100644 --- a/apps/trading/components/select-market/select-market.spec.tsx +++ b/apps/trading/components/select-market/select-market.spec.tsx @@ -178,6 +178,6 @@ describe('SelectMarket', () => { expect(screen.getByText('25.00%')).toBeTruthy(); // price change expect(container).toHaveTextContent(/1,000/); // volume fireEvent.click(screen.getAllByTestId(`market-link-1`)[0]); - expect(onSelect).toHaveBeenCalledWith('1'); + expect(onSelect).toHaveBeenCalledWith('1', false); }); }); diff --git a/apps/trading/components/select-market/select-market.tsx b/apps/trading/components/select-market/select-market.tsx index 24a2d66b1..2a0ede834 100644 --- a/apps/trading/components/select-market/select-market.tsx +++ b/apps/trading/components/select-market/select-market.tsx @@ -40,7 +40,7 @@ export const SelectAllMarketsTableBody = ({ markets?: MarketMaybeWithDataAndCandles[] | null; positions?: PositionFieldsFragment[]; title?: string; - onSelect: (id: string) => void; + onSelect: (id: string, metaKey?: boolean) => void; onCellClick: OnCellClickHandler; headers?: Column[]; tableColumns?: ( @@ -95,7 +95,7 @@ export const SelectMarketPopover = ({ }: { marketCode: string; marketName: string; - onSelect: (id: string) => void; + onSelect: (id: string, metaKey?: boolean) => void; onCellClick: OnCellClickHandler; }) => { const { pubKey } = useVegaWallet(); @@ -116,8 +116,8 @@ export const SelectMarketPopover = ({ skip: !pubKey, }); const onSelectMarket = useCallback( - (marketId: string) => { - onSelect(marketId); + (marketId: string, metaKey?: boolean) => { + onSelect(marketId, metaKey); setOpen(false); }, [onSelect] diff --git a/apps/trading/lib/hooks/use-market-click-handler.ts b/apps/trading/lib/hooks/use-market-click-handler.ts new file mode 100644 index 000000000..946a27889 --- /dev/null +++ b/apps/trading/lib/hooks/use-market-click-handler.ts @@ -0,0 +1,21 @@ +import { useNavigate, useParams, useLocation } from 'react-router-dom'; +import { useCallback } from 'react'; +import { Links, Routes } from '../../pages/client-router'; + +export const useMarketClickHandler = (replace = false) => { + const navigate = useNavigate(); + const { marketId } = useParams(); + const { pathname } = useLocation(); + const isMarketPage = pathname.match(/^\/markets\/(.+)/); + return useCallback( + (selectedId: string, metaKey?: boolean) => { + const link = Links[Routes.MARKET](selectedId); + if (metaKey) { + window.open(`/#${link}`, '_blank'); + } else if (selectedId !== marketId || !isMarketPage) { + navigate(link, { replace }); + } + }, + [navigate, marketId, replace, isMarketPage] + ); +}; diff --git a/apps/trading/lib/hooks/use-vega-transaction-toasts.tsx b/apps/trading/lib/hooks/use-vega-transaction-toasts.tsx index 55b84c85a..2b394c5f5 100644 --- a/apps/trading/lib/hooks/use-vega-transaction-toasts.tsx +++ b/apps/trading/lib/hooks/use-vega-transaction-toasts.tsx @@ -474,15 +474,20 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => { } if (tx.order && tx.order.rejectionReason) { + const rejectionReason = + getRejectionReason(tx.order) || tx.order.rejectionReason || ''; return ( <> {t('Order rejected')} -

- {t( - 'Your order has been rejected because: %s', - getRejectionReason(tx.order) || '' - )} -

+ {rejectionReason || tx.order.rejectionReason ? ( +

+ {t('Your order has been rejected because: %s', [ + rejectionReason || tx.order.rejectionReason, + ])} +

+ ) : ( +

{t('Your order has been rejected.')}

+ )} {tx.txHash && (

{ walletNoConnectionCodes.includes(tx.error.code); if (orderRejection) { label = t('Order rejected'); - errorMessage = t( - 'Your order has been rejected because: %s', - orderRejection - ); + errorMessage = t('Your order has been rejected because: %s', [ + orderRejection || tx.order?.rejectionReason || ' ', + ]); } if (walletError) { label = t('Wallet disconnected'); @@ -645,7 +649,11 @@ export const useVegaTransactionToasts = () => { // Transaction can be successful but the order can be rejected by the network const intent = - tx.order && [OrderStatus.STATUS_REJECTED].includes(tx.order.status) + (tx.order && + [OrderStatus.STATUS_REJECTED, OrderStatus.STATUS_STOPPED].includes( + tx.order.status + )) || + tx.order?.rejectionReason ? Intent.Danger : intentMap[tx.status]; diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index 30f99079c..1eeea5019 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -1,3 +1,5 @@ +import { useMemo, useState } from 'react'; +import classNames from 'classnames'; import Head from 'next/head'; import type { AppProps } from 'next/app'; import { t } from '@vegaprotocol/i18n'; @@ -23,14 +25,12 @@ import { import './styles.css'; import { useGlobalStore, usePageTitleStore } from '../stores'; import { Footer } from '../components/footer'; -import { useMemo, useState } from 'react'; import DialogsContainer from './dialogs-container'; import ToastsManager from './toasts-manager'; import { HashRouter, useLocation, useSearchParams } from 'react-router-dom'; import { Connectors } from '../lib/vega-connectors'; import { ViewingBanner } from '../components/viewing-banner'; import { Banner } from '../components/banner'; -import classNames from 'classnames'; import { AppLoader, DynamicLoader } from '../components/app-loader'; import { Navbar } from '../components/navbar'; @@ -57,7 +57,7 @@ const Title = () => { ); }; -const TransactionsHandler = () => { +const InitializeHandlers = () => { useVegaTransactionManager(); useVegaTransactionUpdater(); useEthTransactionManager(); @@ -93,7 +93,7 @@ function AppBody({ Component }: AppProps) {

- + ); diff --git a/docker-build.sh b/docker-build.sh index 2a4772948..5141384ed 100755 --- a/docker-build.sh +++ b/docker-build.sh @@ -1,10 +1,22 @@ -#!/bin/sh -eux +#!/bin/bash -ex + export PATH="/app/node_modules/.bin:$PATH" + +flags="--network-timeout 100000 --pure-lockfile" + +if [[ ! -z "${ENV_NAME}" ]]; then + flags="--env=${ENV_NAME} $flags" +fi + if [ "${APP}" = "trading" ]; then - yarn nx export ${APP} --network-timeout 100000 --pure-lockfile + yarn nx export ${APP} $flags mv /app/dist/apps/trading/exported/ /app/tmp rm -rf /app/dist/apps/trading mv /app/tmp /app/dist/apps/trading else - yarn nx build ${APP} --network-timeout 100000 --pure-lockfile + yarn nx build ${APP} $flags fi + +env_vars_file="/app/dist/apps/${APP}/.env" +# make sure there are no exposed .env files +rm $env_vars_file || echo "No env vars file" diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index 5360cbd08..000000000 --- a/entrypoint.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -set -e - -# Recreate config file -env_file=/usr/share/nginx/html/assets/env-config.js -mkdir -p $(dirname $env_file) -rm -rf $env_file || echo "no file to delete" -touch $env_file - -env_vars_file=/usr/share/nginx/html/.env -sed -i '/^#/d' $env_vars_file # remove comment lines -sed -i '/^$/d' $env_vars_file # remove empty lines - -# Add assignment -echo "window._env_ = {" >> $env_file - -# Read each line in .env file -# Each line represents key=value pairs -while read -r line || [[ -n "$line" ]]; -do - # Split env variables by character `=` - if printf '%s\n' "$line" | grep -q -e '='; then - varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') - varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') - fi - - # Read value of current variable if exists as Environment variable - value=$(printf '%s\n' "${!varname}") - # Otherwise use value from .env file - [[ -z $value ]] && value=${varvalue} - - # Append configuration property to JS file if non-empty - if [ ! -z "$varname" ]; then - echo " $varname: \"$value\"," >> $env_file - fi -done < $env_vars_file - -rm $env_vars_file - -echo "}" >> $env_file - -# start serving -nginx -g 'daemon off;' diff --git a/libs/datagrid/src/index.ts b/libs/datagrid/src/index.ts index b33afd315..f84344412 100644 --- a/libs/datagrid/src/index.ts +++ b/libs/datagrid/src/index.ts @@ -9,6 +9,7 @@ export * from './lib/cells/price-change-cell'; export * from './lib/cells/price-flash-cell'; export * from './lib/cells/vol-cell'; export * from './lib/cells/centered-grid-cell'; +export * from './lib/cells/market-name-cell'; export * from './lib/filters/date-range-filter'; export * from './lib/filters/set-filter'; diff --git a/libs/datagrid/src/lib/cells/market-name-cell.tsx b/libs/datagrid/src/lib/cells/market-name-cell.tsx new file mode 100644 index 000000000..808273f7f --- /dev/null +++ b/libs/datagrid/src/lib/cells/market-name-cell.tsx @@ -0,0 +1,35 @@ +import type { MouseEvent } from 'react'; +import { useCallback } from 'react'; +import get from 'lodash/get'; + +interface MarketNameCellProps { + value?: string; + data?: { id?: string; marketId?: string; market?: { id: string } }; + idPath?: string; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; +} + +export const MarketNameCell = ({ + value, + data, + idPath, + onMarketClick, +}: MarketNameCellProps) => { + const id = data ? get(data, idPath ?? 'id', 'all') : ''; + const handleOnClick = useCallback( + (ev: MouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + if (onMarketClick) { + onMarketClick(id, ev.metaKey); + } + }, + [id, onMarketClick] + ); + if (!data) return null; + return ( + + ); +}; diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx index e357981ad..16d2a877c 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx @@ -1,4 +1,5 @@ import { Tooltip } from '@vegaprotocol/ui-toolkit'; +import classnames from 'classnames'; import type { ReactNode } from 'react'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import type { Market, MarketData } from '@vegaprotocol/market-list'; @@ -11,9 +12,12 @@ interface DealTicketFeeDetailsProps { order: OrderSubmissionBody['orderSubmission']; market: Market; marketData: MarketData; - margin: string; - totalMargin: string; - balance: string; + currentInitialMargin?: string; + currentMaintenanceMargin?: string; + estimatedInitialMargin: string; + estimatedTotalInitialMargin: string; + marginAccountBalance: string; + generalAccountBalance: string; } export interface DealTicketFeeDetailProps { @@ -45,23 +49,22 @@ export const DealTicketFeeDetails = ({ order, market, marketData, - margin, - totalMargin, - balance, + ...args }: DealTicketFeeDetailsProps) => { const feeDetails = useFeeDealTicketDetails(order, market, marketData); const details = getFeeDetailsValues({ ...feeDetails, - margin, - totalMargin, - balance, + ...args, }); return (
- {details.map(({ label, value, labelDescription, symbol }) => ( + {details.map(({ label, value, labelDescription, symbol, indent }) => (
diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx index 71b99cf2b..385fb33e5 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx @@ -107,6 +107,102 @@ describe('DealTicket', () => { ); }); + it('should set values for a non-persistent reduce only order and disable post only checkbox', () => { + const expectedOrder = { + marketId: market.id, + type: Schema.OrderType.TYPE_LIMIT, + side: Schema.Side.SIDE_SELL, + size: '0.1', + price: '300.22', + timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC, + persist: false, + reduceOnly: true, + postOnly: false, + }; + + useOrderStore.setState({ + orders: { + [expectedOrder.marketId]: expectedOrder, + }, + }); + + render(generateJsx()); + + // Assert correct defaults are used from store + expect( + screen + .getByTestId(`order-type-${Schema.OrderType.TYPE_LIMIT}`) + .querySelector('input') + ).toBeChecked(); + expect( + screen.queryByTestId('order-side-SIDE_SELL')?.querySelector('input') + ).toBeChecked(); + expect( + screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input') + ).not.toBeChecked(); + expect(screen.getByTestId('order-size')).toHaveDisplayValue( + expectedOrder.size + ); + expect(screen.getByTestId('order-tif')).toHaveValue( + expectedOrder.timeInForce + ); + expect(screen.getByTestId('order-price')).toHaveDisplayValue( + expectedOrder.price + ); + expect(screen.getByTestId('post-only')).toBeDisabled(); + expect(screen.getByTestId('reduce-only')).toBeEnabled(); + expect(screen.getByTestId('reduce-only')).toBeChecked(); + expect(screen.getByTestId('post-only')).not.toBeChecked(); + }); + + it('should set values for a persistent post only order and disable reduce only checkbox', () => { + const expectedOrder = { + marketId: market.id, + type: Schema.OrderType.TYPE_LIMIT, + side: Schema.Side.SIDE_SELL, + size: '0.1', + price: '300.22', + timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, + persist: true, + reduceOnly: false, + postOnly: true, + }; + + useOrderStore.setState({ + orders: { + [expectedOrder.marketId]: expectedOrder, + }, + }); + + render(generateJsx()); + + // Assert correct defaults are used from store + expect( + screen + .getByTestId(`order-type-${Schema.OrderType.TYPE_LIMIT}`) + .querySelector('input') + ).toBeChecked(); + expect( + screen.queryByTestId('order-side-SIDE_SELL')?.querySelector('input') + ).toBeChecked(); + expect( + screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input') + ).not.toBeChecked(); + expect(screen.getByTestId('order-size')).toHaveDisplayValue( + expectedOrder.size + ); + expect(screen.getByTestId('order-tif')).toHaveValue( + expectedOrder.timeInForce + ); + expect(screen.getByTestId('order-price')).toHaveDisplayValue( + expectedOrder.price + ); + expect(screen.getByTestId('post-only')).toBeEnabled(); + expect(screen.getByTestId('reduce-only')).toBeDisabled(); + expect(screen.getByTestId('post-only')).toBeChecked(); + expect(screen.getByTestId('reduce-only')).not.toBeChecked(); + }); + it('handles TIF select box dependent on order type', async () => { render(generateJsx()); diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx index 8b6779123..84bb027e5 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx @@ -1,6 +1,6 @@ import { t } from '@vegaprotocol/i18n'; import * as Schema from '@vegaprotocol/types'; -import { memo, useCallback, useEffect, useState, useRef } from 'react'; +import { memo, useCallback, useEffect, useState, useRef, useMemo } from 'react'; import { Controller } from 'react-hook-form'; import { DealTicketAmount } from './deal-ticket-amount'; import { DealTicketButton } from './deal-ticket-button'; @@ -16,10 +16,12 @@ import { useVegaWalletDialogStore, } from '@vegaprotocol/wallet'; import { + Checkbox, ExternalLink, InputError, Intent, Notification, + Tooltip, TinyScroll, } from '@vegaprotocol/ui-toolkit'; @@ -42,6 +44,9 @@ import { import { OrderTimeInForce, OrderType } from '@vegaprotocol/types'; import { useOrderForm } from '../../hooks/use-order-form'; +import { useDataProvider } from '@vegaprotocol/react-helpers'; + +import { marketMarginDataProvider } from '@vegaprotocol/positions'; export interface DealTicketProps { market: Market; @@ -101,6 +106,12 @@ export const DealTicket = ({ const { margin, totalMargin } = useInitialMargin(market.id, normalizedOrder); + const { data: currentMargins } = useDataProvider({ + dataProvider: marketMarginDataProvider, + variables: { marketId: market.id, partyId: pubKey || '' }, + skip: !pubKey, + }); + useEffect(() => { if (!pubKey) { setError('summary', { @@ -146,6 +157,26 @@ export const DealTicket = ({ clearErrors, ]); + const disablePostOnlyCheckbox = useMemo(() => { + const disabled = order + ? [ + Schema.OrderTimeInForce.TIME_IN_FORCE_IOC, + Schema.OrderTimeInForce.TIME_IN_FORCE_FOK, + ].includes(order.timeInForce) + : true; + return disabled; + }, [order]); + + const disableReduceOnlyCheckbox = useMemo(() => { + const disabled = order + ? ![ + Schema.OrderTimeInForce.TIME_IN_FORCE_IOC, + Schema.OrderTimeInForce.TIME_IN_FORCE_FOK, + ].includes(order.timeInForce) + : true; + return disabled; + }, [order]); + const onSubmit = useCallback( (order: OrderSubmission) => { const now = new Date().getTime(); @@ -190,8 +221,18 @@ export const DealTicket = ({ if (type === OrderType.TYPE_NETWORK) return; update({ type, - // when changing type also update the tif to what was last used of new type + // when changing type also update the TIF to what was last used of new type timeInForce: lastTIF[type] || order.timeInForce, + postOnly: + type === OrderType.TYPE_MARKET ? false : order.postOnly, + reduceOnly: + type === OrderType.TYPE_LIMIT && + ![ + OrderTimeInForce.TIME_IN_FORCE_FOK, + OrderTimeInForce.TIME_IN_FORCE_IOC, + ].includes(lastTIF[type] || order.timeInForce) + ? false + : order.postOnly, expiresAt: undefined, }); clearErrors('expiresAt'); @@ -239,8 +280,23 @@ export const DealTicket = ({ value={order.timeInForce} orderType={order.type} onSelect={(timeInForce) => { - update({ timeInForce }); - // Set tif value for the given order type, so that when switching + // Reset post only and reduce only when changing TIF + update({ + timeInForce, + postOnly: [ + OrderTimeInForce.TIME_IN_FORCE_FOK, + OrderTimeInForce.TIME_IN_FORCE_IOC, + ].includes(timeInForce) + ? false + : order.postOnly, + reduceOnly: ![ + OrderTimeInForce.TIME_IN_FORCE_FOK, + OrderTimeInForce.TIME_IN_FORCE_IOC, + ].includes(timeInForce) + ? false + : order.reduceOnly, + }); + // Set TIF value for the given order type, so that when switching // types we know the last used TIF for the given order type setLastTIF((curr) => ({ ...curr, @@ -276,6 +332,70 @@ export const DealTicket = ({ )} /> )} +
+ ( + { + update({ postOnly: !order.postOnly, reduceOnly: false }); + }} + label={ + + {disablePostOnlyCheckbox + ? t( + '"Post only" can not be used on "Fill or Kill" or "Immediate or Cancel" orders.' + ) + : t( + '"Post only" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.' + )} + + } + > + {t('Post only')} + + } + /> + )} + /> + ( + { + update({ postOnly: false, reduceOnly: !order.reduceOnly }); + }} + label={ + + {disableReduceOnlyCheckbox + ? t( + '"Reduce only" can be used only with non-persistent orders, such as "Fill or Kill" or "Immediate or Cancel".' + ) + : t( + '"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.' + )} + + } + > + {t('Reduce only')} + + } + /> + )} + /> +
diff --git a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx index 28a114afe..69b4e11d4 100644 --- a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx @@ -96,16 +96,6 @@ export const TimeInForceSelector = ({ id="select-time-in-force" value={value} onChange={(e) => { - // setPreviousTimeInForce({ - // ...previousTimeInForce, - // [orderType]: e.target.value, - // }); - - // if (previousOrderType !== orderType) { - // setPreviousOrderType(orderType); - // const prev = previousTimeInForce[orderType as OrderType]; - // onSelect(prev); - // } onSelect(e.target.value as Schema.OrderTimeInForce); }} className="w-full" diff --git a/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx b/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx index 0d383edb1..1aeb7e3eb 100644 --- a/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx +++ b/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx @@ -26,7 +26,7 @@ export const compileGridData = ( | 'targetStake' | 'trigger' > | null, - onSelect?: (id: string) => void + onSelect?: (id: string, metaKey?: boolean) => void ): { label: ReactNode; value?: ReactNode }[] => { const grid: SimpleGridProps['grid'] = []; const isLiquidityMonitoringAuction = @@ -78,7 +78,7 @@ export const compileGridData = ( label: ( onSelect && onSelect(market.id)} + onClick={(ev) => onSelect && onSelect(market.id, ev.metaKey)} > {t('Current liquidity')} diff --git a/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx b/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx index 50ce68be6..e0de874da 100644 --- a/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx +++ b/libs/deal-ticket/src/components/trading-mode-tooltip/trading-mode-tooltip.tsx @@ -12,14 +12,16 @@ import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list'; type TradingModeTooltipProps = { marketId?: string; - onSelect?: (marketId: string) => void; + onSelect?: (marketId: string, metaKey?: boolean) => void; skip?: boolean; + skipGrid?: boolean; }; export const TradingModeTooltip = ({ marketId, onSelect, skip, + skipGrid, }: TradingModeTooltipProps) => { const { VEGA_DOCS_URL } = useEnvironment(); const { data: market } = useMarket(marketId); @@ -42,7 +44,7 @@ export const TradingModeTooltip = ({ ); const compiledGrid = - onSelect && compileGridData(market, marketData, onSelect); + !skipGrid && compileGridData(market, marketData, onSelect); switch (marketTradingMode) { case Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS: { @@ -103,6 +105,7 @@ export const TradingModeTooltip = ({ {VEGA_DOCS_URL && ( {t('Find out more')} @@ -129,6 +132,7 @@ export const TradingModeTooltip = ({ createDocsLinks(VEGA_DOCS_URL) .AUCTION_TYPE_LIQUIDITY_MONITORING } + className="ml-1" > {t('Find out more')} @@ -153,6 +157,7 @@ export const TradingModeTooltip = ({ createDocsLinks(VEGA_DOCS_URL) .AUCTION_TYPE_LIQUIDITY_MONITORING } + className="ml-1" > {t('Find out more')} @@ -175,6 +180,7 @@ export const TradingModeTooltip = ({ createDocsLinks(VEGA_DOCS_URL) .AUCTION_TYPE_PRICE_MONITORING } + className="ml-1" > {t('Find out more')} diff --git a/libs/deal-ticket/src/constants.ts b/libs/deal-ticket/src/constants.ts index d2e0058be..a7802bcf8 100644 --- a/libs/deal-ticket/src/constants.ts +++ b/libs/deal-ticket/src/constants.ts @@ -10,12 +10,36 @@ export const EST_MARGIN_TOOLTIP_TEXT = (settlementAsset: string) => export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t( 'Estimated total margin that will cover open position, active orders and this order.' ); -export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance'); +export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance.'); export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) => t( "The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset (%s).", [settlementAsset] ); +export const DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT = ( + settlementAsset: string +) => + t( + 'To cover the required margin, this amount will be drawn from your general (%s) account.', + [settlementAsset] + ); + +export const TOTAL_MARGIN_AVAILABLE = ( + generalAccountBalance: string, + marginAccountBalance: string, + marginMaintenance: string, + settlementAsset: string +) => + t( + 'Total margin available = general %s balance (%s) + margin balance (%s) - maintenance level (%s).', + [ + settlementAsset, + `${generalAccountBalance} ${settlementAsset}`, + `${marginAccountBalance} ${settlementAsset}`, + `${marginMaintenance} ${settlementAsset}`, + ] + ); + export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t( 'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.' ); @@ -40,7 +64,7 @@ export const EST_SLIPPAGE = t( ); export const ERROR_SIZE_DECIMAL = t( - 'The size field accepts up to X decimal places' + 'The size field accepts up to X decimal places.' ); export enum MarketModeValidationType { diff --git a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx b/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx index e9e1d94e2..d099d8e50 100644 --- a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx +++ b/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx @@ -15,6 +15,8 @@ import { NOTIONAL_SIZE_TOOLTIP_TEXT, MARGIN_ACCOUNT_TOOLTIP_TEXT, MARGIN_DIFF_TOOLTIP_TEXT, + DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT, + TOTAL_MARGIN_AVAILABLE, } from '../constants'; import { useOrderCloseOut } from './use-order-closeout'; import { useMarketAccountBalance } from '@vegaprotocol/accounts'; @@ -85,24 +87,32 @@ export const useFeeDealTicketDetails = ( }; export interface FeeDetails { - balance: string; + generalAccountBalance?: string; + marginAccountBalance?: string; market: Market; assetSymbol: string; notionalSize: string | null; estCloseOut: string | null; estimateOrder: EstimateOrderQuery['estimateOrder'] | undefined; - margin: string; - totalMargin: string; + estimatedInitialMargin: string; + estimatedTotalInitialMargin: string; + currentInitialMargin?: string; + currentMaintenanceMargin?: string; } export const getFeeDetailsValues = ({ - balance, + marginAccountBalance, + generalAccountBalance, assetSymbol, estimateOrder, market, notionalSize, - totalMargin, + estimatedTotalInitialMargin, + currentInitialMargin, + currentMaintenanceMargin, }: FeeDetails) => { + const totalBalance = + BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0'); const assetDecimals = market.tradableInstrument.instrument.product.settlementAsset.decimals; const formatValueWithMarketDp = ( @@ -123,7 +133,8 @@ export const getFeeDetailsValues = ({ label: string; value?: string | null; symbol: string; - labelDescription: React.ReactNode; + indent?: boolean; + labelDescription?: React.ReactNode; }[] = [ { label: t('Notional'), @@ -153,38 +164,64 @@ export const getFeeDetailsValues = ({ ), symbol: assetSymbol, }, - /* - { - label: t('Initial margin'), - value: margin && `~${formatValueWithAssetDp(margin)}`, - symbol: assetSymbol, - labelDescription: EST_MARGIN_TOOLTIP_TEXT(assetSymbol), - }, - */ { label: t('Margin required'), value: `~${formatValueWithAssetDp( - balance - ? (BigInt(totalMargin) - BigInt(balance)).toString() - : totalMargin + currentInitialMargin + ? ( + BigInt(estimatedTotalInitialMargin) - BigInt(currentInitialMargin) + ).toString() + : estimatedTotalInitialMargin )}`, symbol: assetSymbol, labelDescription: MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol), }, ]; - if (balance) { + if (totalBalance) { + const totalMarginAvailable = ( + currentMaintenanceMargin + ? totalBalance - BigInt(currentMaintenanceMargin) + : totalBalance + ).toString(); + + details.push({ + indent: true, + label: t('Total margin available'), + value: `~${formatValueWithAssetDp(totalMarginAvailable)}`, + symbol: assetSymbol, + labelDescription: TOTAL_MARGIN_AVAILABLE( + formatValueWithAssetDp(generalAccountBalance), + formatValueWithAssetDp(marginAccountBalance), + formatValueWithAssetDp(currentMaintenanceMargin), + assetSymbol + ), + }); + + if (marginAccountBalance) { + const deductionFromCollateral = + BigInt(estimatedTotalInitialMargin) - BigInt(marginAccountBalance); + + details.push({ + indent: true, + label: t('Deduction from collateral'), + value: `~${formatValueWithAssetDp( + deductionFromCollateral > 0 ? deductionFromCollateral.toString() : '0' + )}`, + symbol: assetSymbol, + labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol), + }); + } + details.push({ label: t('Projected margin'), - value: `~${formatValueWithAssetDp(totalMargin)}`, + value: `~${formatValueWithAssetDp(estimatedTotalInitialMargin)}`, symbol: assetSymbol, labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT, }); } details.push({ label: t('Current margin allocation'), - value: balance - ? `~${formatValueWithAssetDp(balance)}` - : `${formatValueWithAssetDp(balance)}`, + value: `${formatValueWithAssetDp(marginAccountBalance)}`, symbol: assetSymbol, labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT, }); diff --git a/libs/deal-ticket/src/hooks/use-initial-margin.ts b/libs/deal-ticket/src/hooks/use-initial-margin.ts index 834e4eb4e..e7dec56af 100644 --- a/libs/deal-ticket/src/hooks/use-initial-margin.ts +++ b/libs/deal-ticket/src/hooks/use-initial-margin.ts @@ -65,5 +65,11 @@ export const useInitialMargin = ( sellMargin > buyMargin ? sellMargin.toString() : buyMargin.toString(); } - return useMemo(() => ({ totalMargin, margin }), [totalMargin, margin]); + return useMemo( + () => ({ + totalMargin, + margin, + }), + [totalMargin, margin] + ); }; diff --git a/libs/deal-ticket/src/setup-tests.ts b/libs/deal-ticket/src/setup-tests.ts index 068c53d36..e62ea0326 100644 --- a/libs/deal-ticket/src/setup-tests.ts +++ b/libs/deal-ticket/src/setup-tests.ts @@ -1,2 +1,7 @@ import '@testing-library/jest-dom'; import 'jest-canvas-mock'; +import ResizeObserver from 'resize-observer-polyfill'; +import { defaultFallbackInView } from 'react-intersection-observer'; + +defaultFallbackInView(true); +global.ResizeObserver = ResizeObserver; diff --git a/libs/environment/src/components/network-switcher/network-switcher.spec.tsx b/libs/environment/src/components/network-switcher/network-switcher.spec.tsx index a6bf46f6b..84293132b 100644 --- a/libs/environment/src/components/network-switcher/network-switcher.spec.tsx +++ b/libs/environment/src/components/network-switcher/network-switcher.spec.tsx @@ -140,8 +140,6 @@ describe('Network switcher', () => { [Networks.STAGNET3]: 'https://stag3.net', [Networks.DEVNET]: 'https://dev.net', [Networks.STAGNET1]: 'https://stag1.net', - [Networks.SANDBOX]: 'https://sandbox.net', - [Networks.MIRROR]: 'https://mirror.net', }; // @ts-ignore Typescript doesn't know about this module being mocked useEnvironment.mockImplementation(() => ({ @@ -181,8 +179,6 @@ describe('Network switcher', () => { [Networks.STAGNET3]: 'https://stag3.net', [Networks.DEVNET]: 'https://dev.net', [Networks.STAGNET1]: 'https://stag1.net', - [Networks.SANDBOX]: 'https://sandbox.net', - [Networks.MIRROR]: 'https://mirror.net', }; // @ts-ignore Typescript doesn't know about this module being mocked useEnvironment.mockImplementation(() => ({ @@ -215,8 +211,6 @@ describe('Network switcher', () => { [Networks.STAGNET3]: 'https://stag3.net', [Networks.DEVNET]: 'https://dev.net', [Networks.STAGNET1]: 'https://stag1.net', - [Networks.SANDBOX]: 'https://sandbox.net', - [Networks.MIRROR]: 'https://mirror.net', }; // @ts-ignore Typescript doesn't know about this module being mocked useEnvironment.mockImplementation(() => ({ diff --git a/libs/environment/src/components/network-switcher/network-switcher.tsx b/libs/environment/src/components/network-switcher/network-switcher.tsx index ff0531db2..8302a384b 100644 --- a/libs/environment/src/components/network-switcher/network-switcher.tsx +++ b/libs/environment/src/components/network-switcher/network-switcher.tsx @@ -17,12 +17,10 @@ export const envNameMapping: Record = { [Networks.VALIDATOR_TESTNET]: t('VALIDATOR_TESTNET'), [Networks.CUSTOM]: t('Custom'), [Networks.DEVNET]: t('Devnet'), - [Networks.SANDBOX]: t('Sandbox'), [Networks.STAGNET1]: t('Stagnet'), [Networks.STAGNET3]: t('Stagnet3'), [Networks.TESTNET]: t('Fairground testnet'), [Networks.MAINNET]: t('Mainnet'), - [Networks.MIRROR]: t('Mainnet mirror'), }; export const envTriggerMapping: Record = { @@ -32,7 +30,6 @@ export const envTriggerMapping: Record = { export const envDescriptionMapping: Record = { [Networks.CUSTOM]: '', - [Networks.SANDBOX]: t('A playground test environment'), [Networks.VALIDATOR_TESTNET]: t('The validator deployed testnet'), [Networks.DEVNET]: t('The latest Vega code auto-deployed'), [Networks.STAGNET1]: t('A release candidate for the staging environment'), @@ -41,9 +38,6 @@ export const envDescriptionMapping: Record = { 'Public testnet run by the Vega team, often used for incentives' ), [Networks.MAINNET]: t('The vega mainnet'), - [Networks.MIRROR]: t( - 'A mirror of the mainnet environment running on an Ethereum test network' - ), }; const standardNetworkKeys = [Networks.MAINNET, Networks.TESTNET]; diff --git a/libs/environment/src/hooks/use-environment.ts b/libs/environment/src/hooks/use-environment.ts index 5d6fe703f..14f16e441 100644 --- a/libs/environment/src/hooks/use-environment.ts +++ b/libs/environment/src/hooks/use-environment.ts @@ -277,6 +277,7 @@ function compileEnvVars() { ), ETH_LOCAL_PROVIDER_URL: process.env['NX_ETH_LOCAL_PROVIDER_URL'], ETH_WALLET_MNEMONIC: process.env['NX_ETH_WALLET_MNEMONIC'], + ORACLE_PROOFS_URL: process.env['NX_ORACLE_PROOFS_URL'], VEGA_DOCS_URL: process.env['NX_VEGA_DOCS_URL'], VEGA_EXPLORER_URL: process.env['NX_VEGA_EXPLORER_URL'], VEGA_TOKEN_URL: process.env['NX_VEGA_TOKEN_URL'], diff --git a/libs/environment/src/hooks/use-links.ts b/libs/environment/src/hooks/use-links.ts index c2f36b4ae..828c95771 100644 --- a/libs/environment/src/hooks/use-links.ts +++ b/libs/environment/src/hooks/use-links.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { Networks } from '../types'; import { useEnvironment } from './use-environment'; -type Net = Exclude; +type Net = Exclude; export enum DApp { Explorer = 'Explorer', Console = 'Console', @@ -21,7 +21,6 @@ const EmptyLinks: DAppLinks = { [Networks.STAGNET3]: '', [Networks.TESTNET]: '', [Networks.MAINNET]: '', - [Networks.MIRROR]: '', }; const ExplorerLinks = { @@ -61,7 +60,7 @@ export const useLinks = (dapp: DApp, network?: Net) => { }; let net = network || VEGA_ENV; - if (net === Networks.CUSTOM || net === Networks.SANDBOX) { + if (net === Networks.CUSTOM) { net = Networks.TESTNET; } diff --git a/libs/environment/src/types.ts b/libs/environment/src/types.ts index bfd4b78e3..b87b686bd 100644 --- a/libs/environment/src/types.ts +++ b/libs/environment/src/types.ts @@ -5,13 +5,11 @@ import type { envSchema } from './utils/validate-environment'; export enum Networks { VALIDATOR_TESTNET = 'VALIDATOR_TESTNET', CUSTOM = 'CUSTOM', - SANDBOX = 'SANDBOX', TESTNET = 'TESTNET', STAGNET1 = 'STAGNET1', STAGNET3 = 'STAGNET3', DEVNET = 'DEVNET', MAINNET = 'MAINNET', - MIRROR = 'MIRROR', } export type Environment = z.infer; export type Configuration = z.infer; diff --git a/libs/environment/src/utils/validate-environment.ts b/libs/environment/src/utils/validate-environment.ts index 63650543c..af032d4bb 100644 --- a/libs/environment/src/utils/validate-environment.ts +++ b/libs/environment/src/utils/validate-environment.ts @@ -3,13 +3,11 @@ import z from 'zod'; export enum Networks { VALIDATOR_TESTNET = 'VALIDATOR_TESTNET', CUSTOM = 'CUSTOM', - SANDBOX = 'SANDBOX', TESTNET = 'TESTNET', STAGNET1 = 'STAGNET1', STAGNET3 = 'STAGNET3', DEVNET = 'DEVNET', MAINNET = 'MAINNET', - MIRROR = 'MIRROR', } const schemaObject = { @@ -20,6 +18,7 @@ const schemaObject = { GIT_COMMIT_HASH: z.optional(z.string()), GIT_ORIGIN_URL: z.optional(z.string()), GITHUB_FEEDBACK_URL: z.optional(z.string()), + ORACLE_PROOFS_URL: z.optional(z.string().url()), VEGA_ENV: z.nativeEnum(Networks), VEGA_EXPLORER_URL: z.optional(z.string()), VEGA_TOKEN_URL: z.optional(z.string()), diff --git a/libs/fills/src/lib/fills-container.tsx b/libs/fills/src/lib/fills-container.tsx index df759df4a..84a79f462 100644 --- a/libs/fills/src/lib/fills-container.tsx +++ b/libs/fills/src/lib/fills-container.tsx @@ -8,7 +8,7 @@ export const FillsContainer = ({ onMarketClick, }: { marketId?: string; - onMarketClick?: (marketId: string) => void; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; }) => { const { pubKey } = useVegaWallet(); diff --git a/libs/fills/src/lib/fills-manager.tsx b/libs/fills/src/lib/fills-manager.tsx index 51e3dc5a8..8ca9d30af 100644 --- a/libs/fills/src/lib/fills-manager.tsx +++ b/libs/fills/src/lib/fills-manager.tsx @@ -11,7 +11,7 @@ import { useBottomPlaceholder } from '@vegaprotocol/react-helpers'; interface FillsManagerProps { partyId: string; marketId?: string; - onMarketClick?: (marketId: string) => void; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; } export const FillsManager = ({ diff --git a/libs/fills/src/lib/fills-table.tsx b/libs/fills/src/lib/fills-table.tsx index dbfe59d33..eb3090225 100644 --- a/libs/fills/src/lib/fills-table.tsx +++ b/libs/fills/src/lib/fills-table.tsx @@ -14,16 +14,13 @@ import { import { t } from '@vegaprotocol/i18n'; import * as Schema from '@vegaprotocol/types'; import { AgGridColumn } from 'ag-grid-react'; -import { Link } from '@vegaprotocol/ui-toolkit'; import { AgGridDynamic as AgGrid, positiveClassNames, negativeClassNames, + MarketNameCell, } from '@vegaprotocol/datagrid'; -import type { - VegaICellRendererParams, - VegaValueFormatterParams, -} from '@vegaprotocol/datagrid'; +import type { VegaValueFormatterParams } from '@vegaprotocol/datagrid'; import { forwardRef } from 'react'; import BigNumber from 'bignumber.js'; import type { Trade } from './fills-data-provider'; @@ -34,7 +31,7 @@ const MAKER = 'MAKER'; export type Props = (AgGridReactProps | AgReactUiProps) & { partyId: string; - onMarketClick?: (marketId: string) => void; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; }; export const FillsTable = forwardRef( @@ -48,30 +45,14 @@ export const FillsTable = forwardRef( getRowId={({ data }) => data?.id} tooltipShowDelay={0} tooltipHideDelay={2000} + components={{ MarketNameCell }} {...props} > ) => - onMarketClick ? ( - - data?.market?.id && onMarketClick(data?.market?.id) - } - > - {value} - - ) : ( - value - ) - } + cellRenderer="MarketNameCell" + cellRendererParams={{ idPath: 'market.id', onMarketClick }} /> | null } | null } | null }; -export type LiquidityProviderFeeShareUpdateSubscriptionVariables = Types.Exact<{ - marketId: Types.Scalars['ID']; -}>; - - -export type LiquidityProviderFeeShareUpdateSubscription = { __typename?: 'Subscription', marketsData: Array<{ __typename?: 'ObservableMarketData', liquidityProviderFeeShare?: Array<{ __typename?: 'ObservableLiquidityProviderFeeShare', partyId: string, equityLikeShare: string, averageEntryValuation: string }> | null }> }; - export const LiquidityProvisionFieldsFragmentDoc = gql` fragment LiquidityProvisionFields on LiquidityProvision { party { @@ -138,7 +131,7 @@ export type MarketLpQueryResult = Apollo.QueryResult; export type LiquidityProviderFeeShareLazyQueryHookResult = ReturnType; -export type LiquidityProviderFeeShareQueryResult = Apollo.QueryResult; -export const LiquidityProviderFeeShareUpdateDocument = gql` - subscription LiquidityProviderFeeShareUpdate($marketId: ID!) { - marketsData(marketIds: [$marketId]) { - liquidityProviderFeeShare { - partyId - equityLikeShare - averageEntryValuation - } - } -} - `; - -/** - * __useLiquidityProviderFeeShareUpdateSubscription__ - * - * To run a query within a React component, call `useLiquidityProviderFeeShareUpdateSubscription` and pass it any options that fit your needs. - * When your component renders, `useLiquidityProviderFeeShareUpdateSubscription` 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 subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useLiquidityProviderFeeShareUpdateSubscription({ - * variables: { - * marketId: // value for 'marketId' - * }, - * }); - */ -export function useLiquidityProviderFeeShareUpdateSubscription(baseOptions: Apollo.SubscriptionHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useSubscription(LiquidityProviderFeeShareUpdateDocument, options); - } -export type LiquidityProviderFeeShareUpdateSubscriptionHookResult = ReturnType; -export type LiquidityProviderFeeShareUpdateSubscriptionResult = Apollo.SubscriptionResult; \ No newline at end of file +export type LiquidityProviderFeeShareQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/libs/liquidity/src/lib/__generated__/MarketsLiquidity.ts b/libs/liquidity/src/lib/__generated__/MarketsLiquidity.ts index 57c86b73d..47436d32a 100644 --- a/libs/liquidity/src/lib/__generated__/MarketsLiquidity.ts +++ b/libs/liquidity/src/lib/__generated__/MarketsLiquidity.ts @@ -13,7 +13,7 @@ export type LiquidityProvisionMarketsQuery = { __typename?: 'Query', marketsConn export const MarketNodeFragmentDoc = gql` fragment MarketNode on Market { id - liquidityProvisionsConnection { + liquidityProvisionsConnection(live: true) { edges { node { commitmentAmount diff --git a/libs/liquidity/src/lib/liquidity-data-provider.ts b/libs/liquidity/src/lib/liquidity-data-provider.ts index 8e46ccb66..231822c8a 100644 --- a/libs/liquidity/src/lib/liquidity-data-provider.ts +++ b/libs/liquidity/src/lib/liquidity-data-provider.ts @@ -6,7 +6,6 @@ import produce from 'immer'; import { LiquidityProviderFeeShareDocument, - LiquidityProviderFeeShareUpdateDocument, LiquidityProvisionsDocument, LiquidityProvisionsUpdateDocument, MarketLpDocument, @@ -18,7 +17,6 @@ import type { LiquidityProviderFeeShareFieldsFragment, LiquidityProviderFeeShareQuery, LiquidityProviderFeeShareQueryVariables, - LiquidityProviderFeeShareUpdateSubscription, LiquidityProvisionFieldsFragment, LiquidityProvisionsQuery, LiquidityProvisionsQueryVariables, @@ -96,8 +94,8 @@ export const getId = ( > ) => isLpFragment(entry) - ? `${entry.party.id}${entry.status}${entry.createdAt}` - : `${entry.partyID}${entry.status}${entry.createdAt}`; + ? `${entry.party.id}${entry.status}${entry.createdAt}${entry.updatedAt}` + : `${entry.partyID}${entry.status}${entry.createdAt}${entry.updatedAt}`; export const marketLiquidityDataProvider = makeDataProvider< MarketLpQuery, @@ -115,73 +113,94 @@ export const marketLiquidityDataProvider = makeDataProvider< export const liquidityFeeShareDataProvider = makeDataProvider< LiquidityProviderFeeShareQuery, LiquidityProviderFeeShareFieldsFragment[], - LiquidityProviderFeeShareUpdateSubscription, - LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare'], + never, + never, LiquidityProviderFeeShareQueryVariables >({ query: LiquidityProviderFeeShareDocument, - subscriptionQuery: LiquidityProviderFeeShareUpdateDocument, - update: ( - data: LiquidityProviderFeeShareFieldsFragment[] | null, - deltas: LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare'] - ) => { - return produce(data || [], (draft) => { - deltas?.forEach((delta) => { - const id = delta.partyId; - const index = draft.findIndex((a) => a.party.id === id); - if (index !== -1) { - draft[index].equityLikeShare = delta.equityLikeShare; - draft[index].averageEntryValuation = delta.averageEntryValuation; - } else { - draft.unshift({ - equityLikeShare: delta.equityLikeShare, - averageEntryValuation: delta.averageEntryValuation, - party: { - id: delta.partyId, - }, - // TODO add accounts connection to the subscription - }); - } - }); - }); - }, getData: (data) => { return data?.market?.data?.liquidityProviderFeeShare || []; }, - getDelta: (subscriptionData: LiquidityProviderFeeShareUpdateSubscription) => { - return subscriptionData.marketsData[0].liquidityProviderFeeShare; - }, }); +export type Filter = { partyId?: string; active?: boolean }; + export const lpAggregatedDataProvider = makeDerivedDataProvider< - ReturnType, + LiquidityProvisionData[], never, - MarketLpQueryVariables + MarketLpQueryVariables & { filter?: Filter } >( [ - liquidityProvisionsDataProvider, - marketLiquidityDataProvider, - liquidityFeeShareDataProvider, + (callback, client, variables) => + liquidityProvisionsDataProvider(callback, client, { + marketId: variables.marketId, + }), + (callback, client, variables) => + marketLiquidityDataProvider(callback, client, { + marketId: variables.marketId, + }), + (callback, client, variables) => + liquidityFeeShareDataProvider(callback, client, { + marketId: variables.marketId, + }), ], - ([ - liquidityProvisions, - marketLiquidity, - liquidityFeeShare, - ]): LiquidityProvisionData[] => { + ( + [liquidityProvisions, marketLiquidity, liquidityFeeShare], + { filter } + ): LiquidityProvisionData[] => { return getLiquidityProvision( liquidityProvisions, marketLiquidity, - liquidityFeeShare + liquidityFeeShare, + filter ); } ); +export const matchFilter = ( + filter: Filter, + lp: LiquidityProvisionFieldsFragment +) => { + if (filter.partyId && lp.party.id !== filter.partyId) { + return false; + } + if ( + filter.active === true && + lp.status !== Schema.LiquidityProvisionStatus.STATUS_ACTIVE + ) { + return false; + } + if ( + filter.active === false && + lp.status === Schema.LiquidityProvisionStatus.STATUS_ACTIVE + ) { + return false; + } + return true; +}; + export const getLiquidityProvision = ( liquidityProvisions: LiquidityProvisionFieldsFragment[], marketLiquidity: MarketLpQuery, - liquidityFeeShare: LiquidityProviderFeeShareFieldsFragment[] + liquidityFeeShare: LiquidityProviderFeeShareFieldsFragment[], + filter?: Filter ): LiquidityProvisionData[] => { return liquidityProvisions + .filter((lp) => { + if ( + ![ + Schema.LiquidityProvisionStatus.STATUS_ACTIVE, + Schema.LiquidityProvisionStatus.STATUS_UNDEPLOYED, + Schema.LiquidityProvisionStatus.STATUS_PENDING, + ].includes(lp.status) + ) { + return false; + } + if (filter && !matchFilter(filter, lp)) { + return false; + } + return true; + }) .map((lp) => { const market = marketLiquidity?.market; const feeShare = liquidityFeeShare.find( @@ -210,14 +229,7 @@ export const getLiquidityProvision = ( .decimals, balance, }; - }) - .filter((e) => - [ - Schema.LiquidityProvisionStatus.STATUS_ACTIVE, - Schema.LiquidityProvisionStatus.STATUS_UNDEPLOYED, - Schema.LiquidityProvisionStatus.STATUS_PENDING, - ].includes(e.status) - ); + }); }; export interface LiquidityProvisionData diff --git a/libs/market-info/src/components/market-info/MarketInfo.graphql b/libs/market-info/src/components/market-info/MarketInfo.graphql index ef6e9a56a..0801a8ad2 100644 --- a/libs/market-info/src/components/market-info/MarketInfo.graphql +++ b/libs/market-info/src/components/market-info/MarketInfo.graphql @@ -1,3 +1,34 @@ +fragment DataSource on DataSourceDefinition { + sourceType { + ... on DataSourceDefinitionExternal { + sourceType { + ... on DataSourceSpecConfiguration { + signers { + signer { + ... on PubKey { + key + } + ... on ETHAddress { + address + } + } + } + } + } + } + ... on DataSourceDefinitionInternal { + sourceType { + ... on DataSourceSpecConfigurationTime { + conditions { + operator + value + } + } + } + } + } +} + query MarketInfo($marketId: ID!) { market(id: $marketId) { id @@ -79,9 +110,15 @@ query MarketInfo($marketId: ID!) { } dataSourceSpecForSettlementData { id + data { + ...DataSource + } } dataSourceSpecForTradingTermination { id + data { + ...DataSource + } } dataSourceSpecBinding { settlementDataProperty diff --git a/libs/market-info/src/components/market-info/__generated__/MarketInfo.ts b/libs/market-info/src/components/market-info/__generated__/MarketInfo.ts index 1a544aea7..51c75dc13 100644 --- a/libs/market-info/src/components/market-info/__generated__/MarketInfo.ts +++ b/libs/market-info/src/components/market-info/__generated__/MarketInfo.ts @@ -3,14 +3,47 @@ import * as Types from '@vegaprotocol/types'; import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; +export type DataSourceFragment = { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } }; + export type MarketInfoQueryVariables = Types.Exact<{ marketId: Types.Scalars['ID']; }>; -export type MarketInfoQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, lpPriceRange: string, proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string } } } | null> | null } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, riskFactors?: { __typename?: 'RiskFactor', market: string, short: string, long: string } | null, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null } } | null }; - +export type MarketInfoQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, lpPriceRange: string, proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string } } } | null> | null } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, riskFactors?: { __typename?: 'RiskFactor', market: string, short: string, long: string } | null, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null } } | null }; +export const DataSourceFragmentDoc = gql` + fragment DataSource on DataSourceDefinition { + sourceType { + ... on DataSourceDefinitionExternal { + sourceType { + ... on DataSourceSpecConfiguration { + signers { + signer { + ... on PubKey { + key + } + ... on ETHAddress { + address + } + } + } + } + } + } + ... on DataSourceDefinitionInternal { + sourceType { + ... on DataSourceSpecConfigurationTime { + conditions { + operator + value + } + } + } + } + } +} + `; export const MarketInfoDocument = gql` query MarketInfo($marketId: ID!) { market(id: $marketId) { @@ -93,9 +126,15 @@ export const MarketInfoDocument = gql` } dataSourceSpecForSettlementData { id + data { + ...DataSource + } } dataSourceSpecForTradingTermination { id + data { + ...DataSource + } } dataSourceSpecBinding { settlementDataProperty @@ -131,7 +170,7 @@ export const MarketInfoDocument = gql` } } } - `; + ${DataSourceFragmentDoc}`; /** * __useMarketInfoQuery__ diff --git a/libs/market-info/src/components/market-info/info-market.tsx b/libs/market-info/src/components/market-info/info-market.tsx index 3b7ab246b..a36d712fd 100644 --- a/libs/market-info/src/components/market-info/info-market.tsx +++ b/libs/market-info/src/components/market-info/info-market.tsx @@ -39,12 +39,12 @@ import { export interface InfoProps { market: MarketInfoWithDataAndCandles; - onSelect: (id: string) => void; + onSelect?: (id: string, metaKey?: boolean) => void; } export interface MarketInfoContainerProps { marketId: string; - onSelect?: (id: string) => void; + onSelect?: (id: string, metaKey?: boolean) => void; } export const MarketInfoContainer = ({ marketId, @@ -73,7 +73,7 @@ export const MarketInfoContainer = ({ {data ? ( - onSelect?.(id)} /> + ) : ( @@ -85,7 +85,7 @@ export const MarketInfoContainer = ({ }; export const Info = ({ market, onSelect }: InfoProps) => { - const { VEGA_TOKEN_URL, VEGA_EXPLORER_URL } = useEnvironment(); + const { VEGA_TOKEN_URL } = useEnvironment(); const headerClassName = 'uppercase text-lg'; if (!market) return null; @@ -124,6 +124,10 @@ export const Info = ({ market, onSelect }: InfoProps) => { title: t('Instrument'), content: , }, + { + title: t('Oracle'), + content: , + }, { title: t('Settlement asset'), content: , @@ -165,7 +169,7 @@ export const Info = ({ market, onSelect }: InfoProps) => { onSelect(market.id)} + onClick={(ev) => onSelect?.(market.id, ev.metaKey)} data-testid="view-liquidity-link" > {t('View liquidity provision table')} @@ -177,23 +181,6 @@ export const Info = ({ market, onSelect }: InfoProps) => { title: t('Liquidity price range'), content: , }, - { - title: t('Oracle'), - content: ( - - - {t('View settlement data oracle specification')} - - - {t('View termination oracle specification')} - - - ), - }, ]; const marketGovPanels = [ @@ -236,17 +223,17 @@ export const Info = ({ market, onSelect }: InfoProps) => { return (
-

{t('Market data')}

+

{t('Market data')}

-

{t('Market specification')}

+

{t('Market specification')}

{VEGA_TOKEN_URL && market.proposal?.id && (
-

{t('Market governance')}

+

{t('Market governance')}

)} diff --git a/libs/market-info/src/components/market-info/market-info-panels.spec.tsx b/libs/market-info/src/components/market-info/market-info-panels.spec.tsx new file mode 100644 index 000000000..1ddbd3e83 --- /dev/null +++ b/libs/market-info/src/components/market-info/market-info-panels.spec.tsx @@ -0,0 +1,188 @@ +import { render, screen } from '@testing-library/react'; +import { + ConditionOperator, + ConditionOperatorMapping, +} from '@vegaprotocol/types'; +import { DataSourceProof } from './market-info-panels'; + +describe('DataSourceProof', () => { + const ORACLE_PUBKEY = + '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f'; + it('renders correct proof for external data sources', () => { + const props = { + data: { + sourceType: { + __typename: 'DataSourceDefinitionExternal' as const, + sourceType: { + __typename: 'DataSourceSpecConfiguration' as const, + signers: [ + { + __typename: 'Signer' as const, + signer: { + __typename: 'PubKey' as const, + key: ORACLE_PUBKEY, + }, + }, + ], + }, + }, + }, + providers: [ + { + name: 'Another oracle', + url: 'https://zombo.com', + description_markdown: + 'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n', + oracle: { + status: 'GOOD' as const, + status_reason: '', + first_verified: '2022-01-01T00:00:00.000Z', + last_verified: '2022-12-31T00:00:00.000Z', + type: 'public_key' as const, + public_key: ORACLE_PUBKEY, + }, + proofs: [ + { + format: 'signed_message' as const, + available: true, + type: 'public_key' as const, + public_key: ORACLE_PUBKEY, + message: 'SOMEHEX', + }, + ], + github_link: `https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/PubKey-${ORACLE_PUBKEY}.toml`, + }, + ], + type: 'termination' as const, + }; + render(); + expect(screen.getByRole('link')).toHaveAttribute( + 'href', + props.providers[0].github_link + ); + }); + + it('renders message if there are no providers', () => { + const props = { + data: { + sourceType: { + __typename: 'DataSourceDefinitionExternal' as const, + sourceType: { + __typename: 'DataSourceSpecConfiguration' as const, + signers: [ + { + __typename: 'Signer' as const, + signer: { + __typename: 'PubKey' as const, + key: ORACLE_PUBKEY, + }, + }, + ], + }, + }, + }, + providers: [], + type: 'termination' as const, + }; + render(); + expect( + screen.getByText('No oracle proof for termination') + ).toBeInTheDocument(); + }); + + it('renders message if there are no matching proofs', () => { + const props = { + data: { + sourceType: { + __typename: 'DataSourceDefinitionExternal' as const, + sourceType: { + __typename: 'DataSourceSpecConfiguration' as const, + signers: [ + { + __typename: 'Signer' as const, + signer: { + __typename: 'PubKey' as const, + key: ORACLE_PUBKEY, + }, + }, + ], + }, + }, + }, + providers: [ + { + name: 'Another oracle', + url: 'https://zombo.com', + description_markdown: + 'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n', + oracle: { + status: 'GOOD' as const, + status_reason: '', + first_verified: '2022-01-01T00:00:00.000Z', + last_verified: '2022-12-31T00:00:00.000Z', + type: 'public_key' as const, + public_key: 'not-the-pubkey', + }, + proofs: [ + { + format: 'signed_message' as const, + available: true, + type: 'public_key' as const, + public_key: 'not-the-pubkey', + message: 'SOMEHEX', + }, + ], + github_link: `https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/PubKey-${ORACLE_PUBKEY}.toml`, + }, + ], + type: 'settlementData' as const, + }; + render(); + expect( + screen.getByText('No oracle proof for settlement data') + ).toBeInTheDocument(); + }); + + it('renders message if no data source on market', () => { + const props = { + data: { + sourceType: { + __typename: 'Invalid', + }, + }, + providers: [], + type: 'termination' as const, + }; + // @ts-ignore types are invalid + render(); + expect(screen.getByText('Invalid data source')).toBeInTheDocument(); + }); + + it('renders conditions for internal data sources', () => { + const condition = { + __typename: 'Condition' as const, + operator: ConditionOperator.OPERATOR_GREATER_THAN, + value: '100', + }; + const props = { + data: { + sourceType: { + __typename: 'DataSourceDefinitionInternal' as const, + sourceType: { + __typename: 'DataSourceSpecConfigurationTime' as const, + conditions: [condition], + }, + }, + }, + providers: [], + type: 'termination' as const, + }; + render(); + expect(screen.getByText('Internal conditions')).toBeInTheDocument(); + expect( + screen.getByText( + `${ConditionOperatorMapping[condition.operator]} ${condition.value}` + ) + ).toBeInTheDocument(); + }); +}); diff --git a/libs/market-info/src/components/market-info/market-info-panels.tsx b/libs/market-info/src/components/market-info/market-info-panels.tsx index a87a32e10..cb635ef0f 100644 --- a/libs/market-info/src/components/market-info/market-info-panels.tsx +++ b/libs/market-info/src/components/market-info/market-info-panels.tsx @@ -6,7 +6,7 @@ import { calcCandleVolume, totalFeesPercentage, } from '@vegaprotocol/market-list'; -import { Splash } from '@vegaprotocol/ui-toolkit'; +import { ExternalLink, Splash } from '@vegaprotocol/ui-toolkit'; import { addDecimalsFormatNumber, formatNumber, @@ -21,7 +21,12 @@ import type { MarketInfoWithDataAndCandles, } from './market-info-data-provider'; import BigNumber from 'bignumber.js'; +import type { DataSourceDefinition, SignerKind } from '@vegaprotocol/types'; +import { ConditionOperatorMapping } from '@vegaprotocol/types'; import { MarketTradingModeMapping } from '@vegaprotocol/types'; +import { useEnvironment } from '@vegaprotocol/environment'; +import type { Provider } from '@vegaprotocol/oracles'; +import { useOracleProofs } from '@vegaprotocol/oracles'; type PanelProps = Pick< ComponentProps, @@ -399,7 +404,7 @@ export const LiquidityPriceRangeInfoPanel = ({ market.decimalPlaces )} ${quoteUnit}`, }} - > + />
); @@ -408,9 +413,156 @@ export const LiquidityPriceRangeInfoPanel = ({ export const OracleInfoPanel = ({ market, ...props -}: MarketInfoProps & PanelProps) => ( - -); +}: MarketInfoProps & PanelProps) => { + const product = market.tradableInstrument.instrument.product; + const { VEGA_EXPLORER_URL, ORACLE_PROOFS_URL } = useEnvironment(); + const { data } = useOracleProofs(ORACLE_PROOFS_URL); + return ( + +
+ + +
+
+ + {t('View settlement data specification')} + + + {t('View termination specification')} + +
+
+ ); +}; + +export const DataSourceProof = ({ + data, + providers, + type, +}: { + data: DataSourceDefinition; + providers: Provider[] | undefined; + type: 'settlementData' | 'termination'; +}) => { + if (data.sourceType.__typename === 'DataSourceDefinitionExternal') { + const signers = data.sourceType.sourceType.signers || []; + + if (!providers?.length) { + return ; + } + + return ( +
+ {signers.map(({ signer }, i) => { + return ( + + ); + })} +
+ ); + } + + if (data.sourceType.__typename === 'DataSourceDefinitionInternal') { + return ( +
+

{t('Internal conditions')}

+ {data.sourceType.sourceType.conditions.map((condition, i) => { + if (!condition) return null; + return ( +

+ {ConditionOperatorMapping[condition.operator]} {condition.value} +

+ ); + })} +
+ ); + } + + return
{t('Invalid data source')}
; +}; + +const OracleLink = ({ + providers, + signer, + type, + index, +}: { + providers: Provider[]; + signer: SignerKind; + type: 'settlementData' | 'termination'; + index: number; +}) => { + const text = + type === 'settlementData' + ? t('View settlement oracle details') + : t('View termination oracle details'); + const textWithCount = index > 0 ? `${text} (${index + 1})` : text; + + const provider = providers.find((p) => { + if (signer.__typename === 'PubKey') { + if ( + p.oracle.type === 'public_key' && + p.oracle.public_key === signer.key + ) { + return true; + } + } + + if (signer.__typename === 'ETHAddress') { + if ( + p.oracle.type === 'eth_address' && + p.oracle.eth_address === signer.address + ) { + return true; + } + } + + return false; + }); + + if (!provider) { + return ; + } + + return ( +

+ {textWithCount} +

+ ); +}; + +const NoOracleProof = ({ + type, +}: { + type: 'settlementData' | 'termination'; +}) => { + return ( +

+ {t( + 'No oracle proof for %s', + type === 'settlementData' ? 'settlement data' : 'termination' + )} +

+ ); +}; diff --git a/libs/market-info/src/components/market-info/market-info.mock.ts b/libs/market-info/src/components/market-info/market-info.mock.ts index fdf4f6817..102597d09 100644 --- a/libs/market-info/src/components/market-info/market-info.mock.ts +++ b/libs/market-info/src/components/market-info/market-info.mock.ts @@ -133,10 +133,44 @@ export const marketInfoQuery = ( dataSourceSpecForSettlementData: { __typename: 'DataSourceSpec', id: 'f028fe5ea7de3890962a05a7163fdde562629af649ed81b8c8902fafb6eef04f', + data: { + sourceType: { + __typename: 'DataSourceDefinitionExternal', + sourceType: { + __typename: 'DataSourceSpecConfiguration', + signers: [ + { + __typename: 'Signer', + signer: { + __typename: 'PubKey', + key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f', + }, + }, + ], + }, + }, + }, }, dataSourceSpecForTradingTermination: { __typename: 'DataSourceSpec', id: 'f028fe5ea7de3890962a05a7163fdde562629af649ed81b8c8902fafb6eef04f', + data: { + sourceType: { + __typename: 'DataSourceDefinitionExternal', + sourceType: { + __typename: 'DataSourceSpecConfiguration', + signers: [ + { + __typename: 'Signer', + signer: { + __typename: 'PubKey', + key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f', + }, + }, + ], + }, + }, + }, }, dataSourceSpecBinding: { __typename: 'DataSourceSpecToFutureBinding', diff --git a/libs/market-list/src/lib/components/markets-container/market-list-table.tsx b/libs/market-list/src/lib/components/markets-container/market-list-table.tsx index 358c1daa3..faa6fe978 100644 --- a/libs/market-list/src/lib/components/markets-container/market-list-table.tsx +++ b/libs/market-list/src/lib/components/markets-container/market-list-table.tsx @@ -10,6 +10,7 @@ import type { import { AgGridDynamic as AgGrid, PriceFlashCell, + MarketNameCell, } from '@vegaprotocol/datagrid'; import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { AgGridColumn } from 'ag-grid-react'; @@ -24,8 +25,10 @@ export const getRowId = ({ data }: { data: { id: string } }) => data.id; export const MarketListTable = forwardRef< AgGridReact, - TypedDataAgGrid ->((props, ref) => { + TypedDataAgGrid & { + onMarketClick: (marketId: string, metaKey?: boolean) => void; + } +>(({ onMarketClick, ...props }, ref) => { const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); return ( ) => { - if (!data) return null; - return {value}; - }} + cellRenderer="MarketNameCell" + cellRendererParams={{ onMarketClick }} /> void; + onSelect: (marketId: string, metaKey?: boolean) => void; } export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => { @@ -21,16 +23,23 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => { rowData={error ? [] : data} suppressLoadingOverlay suppressNoRowsOverlay - onRowClicked={(rowEvent: RowClickedEvent) => { - const { data, event } = rowEvent; - // filters out clicks on the symbol column because it should display asset details + onCellClicked={(cellEvent: CellClickedEvent) => { + const { data, column, event } = cellEvent; + const colId = column.getColId(); if ( - (event?.target as HTMLElement).tagName.toUpperCase() === 'BUTTON' + [ + 'tradableInstrument.instrument.code', + 'tradableInstrument.instrument.product.settlementAsset', + ].includes(colId) ) { return; } - onSelect((data as MarketMaybeWithData).id); + onSelect( + (data as MarketMaybeWithData).id, + (event as unknown as MouseEvent)?.metaKey + ); }} + onMarketClick={onSelect} />
; +export type Oracle = z.infer; +export type Proof = z.infer; +export type Status = z.infer; + +const statusSchema = z.enum([ + 'UNKNOWN', + 'GOOD', + 'SUSPICIOUS', + 'MALICIOUS', + 'RETIRED', + 'COMPROMISED', +]); + +const baseProofSchema = z.object({ + format: z.enum(['url', 'signed_message']), + available: z.boolean(), +}); + +const proofSchema = z.discriminatedUnion('type', [ + baseProofSchema.extend({ + type: z.literal('public_key'), + public_key: z.string().min(64), + message: z.string().min(1), + }), + baseProofSchema.extend({ + type: z.literal('eth_address'), + eth_address: z.string().min(42), + message: z.string().min(1), + }), + baseProofSchema.extend({ + type: z.literal('web'), + url: z.string().url(), + }), + baseProofSchema.extend({ + type: z.literal('github'), + url: z.string().url(), + }), + baseProofSchema.extend({ + type: z.literal('twitter'), + url: z.string().url(), + }), +]); + +const baseOracleSchema = z.object({ + status: statusSchema, + status_reason: z.string(), + first_verified: z.string(), + last_verified: z.string(), +}); + +const oracleSchema = z.discriminatedUnion('type', [ + baseOracleSchema.extend({ + type: z.literal('public_key'), + public_key: z.string().min(64), + }), + baseOracleSchema.extend({ + type: z.literal('eth_address'), + eth_address: z.string().min(42), + }), +]); + +const providerSchema = z.object({ + name: z.string().min(1), + url: z.string().url(), + description_markdown: z.string(), + oracle: oracleSchema, + proofs: z.array(proofSchema), + github_link: z.string().url(), +}); + +export const providersSchema = z.array(providerSchema); diff --git a/libs/oracles/src/lib/use-oracle-proofs.spec.ts b/libs/oracles/src/lib/use-oracle-proofs.spec.ts new file mode 100644 index 000000000..d377fdb15 --- /dev/null +++ b/libs/oracles/src/lib/use-oracle-proofs.spec.ts @@ -0,0 +1,135 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import type { Provider } from './oracle-schema'; +import { useOracleProofs, cache, invalidateCache } from './use-oracle-proofs'; + +global.fetch = jest.fn(); +const mockFetch = global.fetch as jest.Mock; + +const createOracleData = (): Provider[] => { + return [ + { + name: 'Another oracle', + url: 'https://zombo.com', + description_markdown: + 'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n', + oracle: { + status: 'GOOD', + status_reason: '', + first_verified: '2022-01-01T00:00:00.000Z', + last_verified: '2022-12-31T00:00:00.000Z', + type: 'public_key', + public_key: + '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f', + }, + proofs: [ + { + format: 'url', + available: true, + type: 'twitter', + url: 'https://twitter.com/vegaprotocol/status/956833487230730241', + }, + { + format: 'signed_message', + available: true, + type: 'public_key', + public_key: + '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f', + message: 'SOMEHEX', + }, + ], + github_link: + 'https://github.com/vegaprotocol/well-known/blob/feat/add-process-script/oracle-providers/PubKey-69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f.toml', + }, + ]; +}; + +describe('useOracleProofs', () => { + const url = 'https://foo.bar.com'; + const setup = (data: Provider[]) => { + mockFetch.mockImplementation(() => { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(data), + }); + }); + return renderHook(() => useOracleProofs(url)); + }; + + beforeEach(() => { + mockFetch.mockClear(); + }); + + describe('fetches and caches', () => { + it('fetches oracle data', async () => { + const data = createOracleData(); + const { result } = setup(data); + + expect(result.current.data).toBe(undefined); + expect(result.current.error).toBe(undefined); + expect(result.current.loading).toBe(true); + + await waitFor(() => { + expect(result.current.data).toEqual(data); + expect(result.current.error).toBe(undefined); + expect(result.current.loading).toBe(false); + }); + + expect(mockFetch).toHaveBeenCalledTimes(1); + + // check result was cached + expect(cache).toEqual({ [url]: data }); + }); + + it('uses cached value if present', () => { + const data = createOracleData(); + const { result } = setup(data); + + expect(result.current.data).toEqual(data); + expect(result.current.error).toBe(undefined); + expect(result.current.loading).toBe(false); + + expect(mockFetch).toHaveBeenCalledTimes(0); + }); + }); + + it('handles invalid payload', async () => { + invalidateCache(); + // @ts-ignore enforce invalid result + const { result } = setup([{ invalid: 'result' }]); + + expect(result.current.data).toBe(undefined); + expect(result.current.error).toBe(undefined); + expect(result.current.loading).toBe(true); + + await waitFor(() => { + expect(result.current.data).toBe(undefined); + expect(result.current.error instanceof Error).toBe(true); + expect(result.current.loading).toBe(false); + }); + + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it('handles failed to fetch', async () => { + invalidateCache(); + + mockFetch.mockImplementation(() => { + return Promise.reject(new Error('failed to fetch')); + }); + + const { result } = renderHook(() => useOracleProofs(url)); + + expect(result.current.data).toBe(undefined); + expect(result.current.error).toBe(undefined); + expect(result.current.loading).toBe(true); + + await waitFor(() => { + expect(result.current.data).toBe(undefined); + expect(result.current.error instanceof Error).toBe(true); + expect(result.current.error).toEqual(new Error('failed to fetch')); + expect(result.current.loading).toBe(false); + }); + + expect(mockFetch).toHaveBeenCalledTimes(1); + }); +}); diff --git a/libs/oracles/src/lib/use-oracle-proofs.ts b/libs/oracles/src/lib/use-oracle-proofs.ts new file mode 100644 index 000000000..7953743c2 --- /dev/null +++ b/libs/oracles/src/lib/use-oracle-proofs.ts @@ -0,0 +1,64 @@ +import { useEffect, useState } from 'react'; +import type { Provider } from './oracle-schema'; +import { providersSchema } from './oracle-schema'; + +export let cache: { + [url: string]: Provider[]; +} = {}; + +export const useOracleProofs = (url?: string) => { + const [data, setData] = useState(() => + url ? cache[url] : undefined + ); + const [status, setStatus] = useState<'idle' | 'loading' | 'done'>('idle'); + const [error, setError] = useState(); + + useEffect(() => { + let ignore = false; + + if (!url) return; + + const run = async () => { + try { + if (cache[url]) { + setData(cache[url]); + } else { + setStatus('loading'); + const res = await fetch(url); + const json = await res.json(); + + if (ignore) return; + + const result = providersSchema.parse(json); + + cache[url] = result; + setData(result); + } + } catch (err) { + if (err instanceof Error) { + setError(err); + } else { + setError(new Error('Something went wrong')); + } + } finally { + setStatus('done'); + } + }; + + run(); + + return () => { + ignore = true; + }; + }, [url]); + + return { + data, + loading: status === 'loading', + error, + }; +}; + +export const invalidateCache = () => { + cache = {}; +}; diff --git a/libs/oracles/tsconfig.json b/libs/oracles/tsconfig.json new file mode 100644 index 000000000..4c089585e --- /dev/null +++ b/libs/oracles/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/oracles/tsconfig.lib.json b/libs/oracles/tsconfig.lib.json new file mode 100644 index 000000000..af84f21cf --- /dev/null +++ b/libs/oracles/tsconfig.lib.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "jest.config.ts", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/oracles/tsconfig.spec.json b/libs/oracles/tsconfig.spec.json new file mode 100644 index 000000000..ff08addd6 --- /dev/null +++ b/libs/oracles/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/libs/orders/src/lib/components/order-data-provider/Orders.graphql b/libs/orders/src/lib/components/order-data-provider/Orders.graphql index 08a06fa6f..67ba24909 100644 --- a/libs/orders/src/lib/components/order-data-provider/Orders.graphql +++ b/libs/orders/src/lib/components/order-data-provider/Orders.graphql @@ -14,6 +14,8 @@ fragment OrderFields on Order { expiresAt createdAt updatedAt + postOnly + reduceOnly liquidityProvision { __typename } @@ -30,12 +32,16 @@ query OrderById($orderId: ID!) { query Orders( $partyId: ID! + $marketIds: [ID!] $pagination: Pagination - $filter: OrderByMarketIdsFilter + $filter: OrderFilter ) { party(id: $partyId) { id - ordersConnection(pagination: $pagination, filter: $filter) { + ordersConnection( + pagination: $pagination + filter: { order: $filter, marketIds: $marketIds } + ) { edges { node { ...OrderFields @@ -72,8 +78,8 @@ fragment OrderUpdateFields on OrderUpdate { } } -subscription OrdersUpdate($partyId: ID!) { - orders(filter: { partyIds: [$partyId] }) { +subscription OrdersUpdate($partyId: ID!, $marketIds: [ID!]) { + orders(filter: { partyIds: [$partyId], marketIds: $marketIds }) { ...OrderUpdateFields } } diff --git a/libs/orders/src/lib/components/order-data-provider/__generated__/Orders.ts b/libs/orders/src/lib/components/order-data-provider/__generated__/Orders.ts index 6b89f9412..6b91e2f07 100644 --- a/libs/orders/src/lib/components/order-data-provider/__generated__/Orders.ts +++ b/libs/orders/src/lib/components/order-data-provider/__generated__/Orders.ts @@ -3,28 +3,30 @@ import * as Types from '@vegaprotocol/types'; import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; -export type OrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null }; +export type OrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null }; export type OrderByIdQueryVariables = Types.Exact<{ orderId: Types.Scalars['ID']; }>; -export type OrderByIdQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } }; +export type OrderByIdQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } }; export type OrdersQueryVariables = Types.Exact<{ partyId: Types.Scalars['ID']; + marketIds?: Types.InputMaybe | Types.Scalars['ID']>; pagination?: Types.InputMaybe; - filter?: Types.InputMaybe; + filter?: Types.InputMaybe; }>; -export type OrdersQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, ordersConnection?: { __typename?: 'OrderConnection', edges?: Array<{ __typename?: 'OrderEdge', cursor?: string | null, node: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } }> | null, pageInfo?: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } | null } | null } | null }; +export type OrdersQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, ordersConnection?: { __typename?: 'OrderConnection', edges?: Array<{ __typename?: 'OrderEdge', cursor?: string | null, node: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } }> | null, pageInfo?: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } | null } | null } | null }; export type OrderUpdateFieldsFragment = { __typename?: 'OrderUpdate', id: string, marketId: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, liquidityProvisionId?: string | null, peggedOrder?: { __typename: 'PeggedOrder' } | null }; export type OrdersUpdateSubscriptionVariables = Types.Exact<{ partyId: Types.Scalars['ID']; + marketIds?: Types.InputMaybe | Types.Scalars['ID']>; }>; @@ -47,6 +49,8 @@ export const OrderFieldsFragmentDoc = gql` expiresAt createdAt updatedAt + postOnly + reduceOnly liquidityProvision { __typename } @@ -112,10 +116,13 @@ export type OrderByIdQueryHookResult = ReturnType; export type OrderByIdLazyQueryHookResult = ReturnType; export type OrderByIdQueryResult = Apollo.QueryResult; export const OrdersDocument = gql` - query Orders($partyId: ID!, $pagination: Pagination, $filter: OrderByMarketIdsFilter) { + query Orders($partyId: ID!, $marketIds: [ID!], $pagination: Pagination, $filter: OrderFilter) { party(id: $partyId) { id - ordersConnection(pagination: $pagination, filter: $filter) { + ordersConnection( + pagination: $pagination + filter: {order: $filter, marketIds: $marketIds} + ) { edges { node { ...OrderFields @@ -146,6 +153,7 @@ export const OrdersDocument = gql` * const { data, loading, error } = useOrdersQuery({ * variables: { * partyId: // value for 'partyId' + * marketIds: // value for 'marketIds' * pagination: // value for 'pagination' * filter: // value for 'filter' * }, @@ -163,8 +171,8 @@ export type OrdersQueryHookResult = ReturnType; export type OrdersLazyQueryHookResult = ReturnType; export type OrdersQueryResult = Apollo.QueryResult; export const OrdersUpdateDocument = gql` - subscription OrdersUpdate($partyId: ID!) { - orders(filter: {partyIds: [$partyId]}) { + subscription OrdersUpdate($partyId: ID!, $marketIds: [ID!]) { + orders(filter: {partyIds: [$partyId], marketIds: $marketIds}) { ...OrderUpdateFields } } @@ -183,6 +191,7 @@ export const OrdersUpdateDocument = gql` * const { data, loading, error } = useOrdersUpdateSubscription({ * variables: { * partyId: // value for 'partyId' + * marketIds: // value for 'marketIds' * }, * }); */ diff --git a/libs/orders/src/lib/components/order-data-provider/order-data-provider.spec.ts b/libs/orders/src/lib/components/order-data-provider/order-data-provider.spec.ts index 436967f77..10b650436 100644 --- a/libs/orders/src/lib/components/order-data-provider/order-data-provider.spec.ts +++ b/libs/orders/src/lib/components/order-data-provider/order-data-provider.spec.ts @@ -112,9 +112,7 @@ describe('order data provider', () => { const updatedData = update(data, delta, () => null, { partyId: '0x123', filter: { - order: { - dateRange: { end: new Date('2022-02-01').toISOString() }, - }, + dateRange: { end: new Date('2022-02-01').toISOString() }, }, }); expect( diff --git a/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts b/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts index 7065782a1..833769829 100644 --- a/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts +++ b/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts @@ -35,45 +35,40 @@ const orderMatchFilters = ( return true; } if ( - variables?.filter?.order?.status && - !(order.status && variables.filter.order.status.includes(order.status)) + variables?.filter?.status && + !(order.status && variables.filter.status.includes(order.status)) ) { return false; } if ( - variables?.filter?.order?.types && - !(order.type && variables.filter.order.types.includes(order.type)) + variables?.filter?.types && + !(order.type && variables.filter.types.includes(order.type)) ) { return false; } if ( - variables?.filter?.order?.timeInForce && - !variables.filter.order.timeInForce.includes(order.timeInForce) + variables?.filter?.timeInForce && + !variables.filter.timeInForce.includes(order.timeInForce) ) { return false; } - if ( - variables?.filter?.order?.excludeLiquidity && - order.liquidityProvisionId - ) { + if (variables?.filter?.excludeLiquidity && order.liquidityProvisionId) { return false; } if ( - variables?.filter?.order?.dateRange?.start && + variables?.filter?.dateRange?.start && !( (order.updatedAt || order.createdAt) && - variables.filter.order.dateRange.start < - (order.updatedAt || order.createdAt) + variables.filter.dateRange.start < (order.updatedAt || order.createdAt) ) ) { return false; } if ( - variables?.filter?.order?.dateRange?.end && + variables?.filter?.dateRange?.end && !( (order.updatedAt || order.createdAt) && - variables.filter.order.dateRange.end > - (order.updatedAt || order.createdAt) + variables.filter.dateRange.end > (order.updatedAt || order.createdAt) ) ) { return false; @@ -243,18 +238,17 @@ export const hasActiveOrderProvider = makeDerivedDataProvider< { partyId: string; marketId?: string } >( [ - (callback, client, variables) => + (callback, client, { partyId, marketId }) => hasActiveOrderProviderInternal(callback, client, { + marketIds: marketId ? [marketId] : undefined, filter: { - order: { - status: [OrderStatus.STATUS_ACTIVE], - excludeLiquidity: true, - }, + status: [OrderStatus.STATUS_ACTIVE], + excludeLiquidity: true, }, pagination: { first: 1, }, - ...variables, + partyId, } as OrdersQueryVariables), ], (parts) => parts[0] diff --git a/libs/orders/src/lib/components/order-list-container.tsx b/libs/orders/src/lib/components/order-list-container.tsx index 018558702..86689da45 100644 --- a/libs/orders/src/lib/components/order-list-container.tsx +++ b/libs/orders/src/lib/components/order-list-container.tsx @@ -9,7 +9,7 @@ export const OrderListContainer = ({ enforceBottomPlaceholder, }: { marketId?: string; - onMarketClick?: (marketId: string) => void; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; enforceBottomPlaceholder?: boolean; }) => { const { pubKey, isReadOnly } = useVegaWallet(); diff --git a/libs/orders/src/lib/components/order-list-manager/order-list-manager.tsx b/libs/orders/src/lib/components/order-list-manager/order-list-manager.tsx index 6b2706e34..4be809922 100644 --- a/libs/orders/src/lib/components/order-list-manager/order-list-manager.tsx +++ b/libs/orders/src/lib/components/order-list-manager/order-list-manager.tsx @@ -23,7 +23,7 @@ import type { Order, OrderEdge } from '../order-data-provider'; export interface OrderListManagerProps { partyId: string; marketId?: string; - onMarketClick?: (marketId: string) => void; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; isReadOnly: boolean; enforceBottomPlaceholder?: boolean; } diff --git a/libs/orders/src/lib/components/order-list-manager/use-order-list-data.ts b/libs/orders/src/lib/components/order-list-manager/use-order-list-data.ts index 00e16c27e..060a9180a 100644 --- a/libs/orders/src/lib/components/order-list-manager/use-order-list-data.ts +++ b/libs/orders/src/lib/components/order-list-manager/use-order-list-data.ts @@ -72,12 +72,10 @@ export const useOrderListData = ({ const allVars: OrdersQueryVariables & OrdersUpdateSubscriptionVariables = { partyId, filter: { - order: { - dateRange: filter?.updatedAt?.value, - status: filter?.status?.value, - timeInForce: filter?.timeInForce?.value, - types: filter?.type?.value, - }, + dateRange: filter?.updatedAt?.value, + status: filter?.status?.value, + timeInForce: filter?.timeInForce?.value, + types: filter?.type?.value, }, pagination: { first: 1000, diff --git a/libs/orders/src/lib/components/order-list/order-list.tsx b/libs/orders/src/lib/components/order-list/order-list.tsx index c1d2e7ec6..3d484e15d 100644 --- a/libs/orders/src/lib/components/order-list/order-list.tsx +++ b/libs/orders/src/lib/components/order-list/order-list.tsx @@ -5,7 +5,7 @@ import { } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; import * as Schema from '@vegaprotocol/types'; -import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit'; +import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { AgGridColumn } from 'ag-grid-react'; import BigNumber from 'bignumber.js'; import { memo, forwardRef } from 'react'; @@ -15,6 +15,7 @@ import { DateRangeFilter, negativeClassNames, positiveClassNames, + MarketNameCell, } from '@vegaprotocol/datagrid'; import type { TypedDataAgGrid, @@ -29,7 +30,7 @@ type OrderListProps = TypedDataAgGrid & { marketId?: string }; export type OrderListTableProps = OrderListProps & { cancel: (order: Order) => void; setEditOrder: (order: Order) => void; - onMarketClick?: (marketId: string) => void; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; isReadOnly: boolean; }; @@ -50,30 +51,14 @@ export const OrderListTable = memo( height: '100%', }} getRowId={({ data }) => data.id} + components={{ MarketNameCell }} {...props} > ) => - onMarketClick ? ( - - data?.market?.id && onMarketClick(data?.market?.id) - } - > - {value} - - ) : ( - value - ) - } + cellRenderer="MarketNameCell" + cellRendererParams={{ idPath: 'market.id', onMarketClick }} minWidth={150} /> ) => { if (data?.rejectionReason && value) { return `${Schema.OrderStatusMapping[value]}: ${ - data?.rejectionReason && - Schema.OrderRejectionReasonMapping[data.rejectionReason] + (data?.rejectionReason && + Schema.OrderRejectionReasonMapping[data.rejectionReason]) || + data?.rejectionReason }`; } return value ? Schema.OrderStatusMapping[value] : ''; @@ -233,7 +219,14 @@ export const OrderListTable = memo( return `${Schema.OrderTimeInForceMapping[value]}: ${expiry}`; } - return value ? Schema.OrderTimeInForceMapping[value] : ''; + const tifLabel = value + ? Schema.OrderTimeInForceMapping[value] + : ''; + const label = `${tifLabel}${ + data?.postOnly ? t('. Post Only') : '' + }${data?.reduceOnly ? t('. Reduce only') : ''}`; + + return label; }} minWidth={150} /> diff --git a/libs/orders/src/lib/order-hooks/use-order-store.ts b/libs/orders/src/lib/order-hooks/use-order-store.ts index c79c29d31..00dae623f 100644 --- a/libs/orders/src/lib/order-hooks/use-order-store.ts +++ b/libs/orders/src/lib/order-hooks/use-order-store.ts @@ -13,6 +13,8 @@ export type OrderObj = { price?: string; expiresAt?: string | undefined; persist: boolean; // key used to determine if order should be kept in localStorage + postOnly?: boolean; + reduceOnly?: boolean; }; type OrderMap = { [marketId: string]: OrderObj | undefined }; @@ -114,4 +116,6 @@ export const getDefaultOrder = (marketId: string): OrderObj => ({ price: '0', expiresAt: undefined, persist: false, + postOnly: false, + reduceOnly: false, }); diff --git a/libs/orders/src/lib/order-hooks/use-pending-orders-volume.ts b/libs/orders/src/lib/order-hooks/use-pending-orders-volume.ts index efcb3513d..988570419 100644 --- a/libs/orders/src/lib/order-hooks/use-pending-orders-volume.ts +++ b/libs/orders/src/lib/order-hooks/use-pending-orders-volume.ts @@ -53,14 +53,12 @@ export const useActiveOrdersVolumeAndMargin = ( update, variables: { partyId: partyId || '', + marketIds: [marketId], filter: { - marketIds: [marketId], - order: { - status: [ - OrderStatus.STATUS_ACTIVE, - OrderStatus.STATUS_PARTIALLY_FILLED, - ], - }, + status: [ + OrderStatus.STATUS_ACTIVE, + OrderStatus.STATUS_PARTIALLY_FILLED, + ], }, }, skip: !partyId, diff --git a/libs/positions/src/index.ts b/libs/positions/src/index.ts index 3608ec9cf..b7991151b 100644 --- a/libs/positions/src/index.ts +++ b/libs/positions/src/index.ts @@ -4,7 +4,6 @@ export * from './lib/positions-data-providers'; export * from './lib/margin-data-provider'; export * from './lib/margin-calculator'; export * from './lib/positions-table'; -export * from './lib/use-close-position'; export * from './lib/use-market-margin'; export * from './lib/use-market-position-open-volume'; export * from './lib/use-open-volume'; diff --git a/libs/positions/src/lib/close-position-dialog/complete.tsx b/libs/positions/src/lib/close-position-dialog/complete.tsx deleted file mode 100644 index 2bc3bc23e..000000000 --- a/libs/positions/src/lib/close-position-dialog/complete.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { useEnvironment } from '@vegaprotocol/environment'; -import type { OrderFieldsFragment } from '@vegaprotocol/orders'; -import { truncateByChars } from '@vegaprotocol/utils'; -import { t } from '@vegaprotocol/i18n'; -import * as Schema from '@vegaprotocol/types'; -import { Link } from '@vegaprotocol/ui-toolkit'; -import type { TransactionResult, VegaTxState } from '@vegaprotocol/wallet'; -import type { ClosingOrder as IClosingOrder } from '../use-close-position'; -import { useRequestClosePositionData } from '../use-request-close-position-data'; -import { ClosingOrder } from './shared'; - -interface CompleteProps { - partyId: string; - transaction: VegaTxState; - transactionResult?: TransactionResult; - closingOrder?: IClosingOrder; - closingOrderResult?: OrderFieldsFragment; -} - -export const Complete = ({ - partyId, - transaction, - transactionResult, - closingOrder, - closingOrderResult, -}: CompleteProps) => { - const { VEGA_EXPLORER_URL } = useEnvironment(); - - if (!transactionResult || !closingOrderResult) return null; - - return ( - <> - {closingOrderResult.status === Schema.OrderStatus.STATUS_FILLED && - transactionResult.status ? ( - - ) : ( - - )} - {transaction.txHash && ( - <> -

{t('Transaction')}

-

- - {truncateByChars(transaction.txHash)} - -

- - )} - - ); -}; - -const Success = ({ - partyId, - order, -}: { - partyId: string; - order?: IClosingOrder; -}) => { - const { market, marketData, orders } = useRequestClosePositionData( - order?.marketId, - partyId - ); - - if (!market || !marketData || !orders) { - return
{t('Loading...')}
; - } - - if (!order) { - return ( -
{t('Could retrieve closing order')}
- ); - } - return ( - <> -

{t('Position closed')}

- - - ); -}; - -const Error = ({ - transactionResult, - closingOrderResult, -}: { - transactionResult: TransactionResult; - closingOrderResult: OrderFieldsFragment; -}) => { - const reason = - closingOrderResult.rejectionReason && - Schema.OrderRejectionReasonMapping[closingOrderResult.rejectionReason]; - return ( -
- {reason ? ( -

{reason}

- ) : ( -

- {t('Transaction failed')}: {transactionResult.error} -

- )} -
- ); -}; diff --git a/libs/positions/src/lib/close-position-dialog/requested.spec.tsx b/libs/positions/src/lib/close-position-dialog/requested.spec.tsx deleted file mode 100644 index 8d1f3dad1..000000000 --- a/libs/positions/src/lib/close-position-dialog/requested.spec.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { render, screen, within } from '@testing-library/react'; -import * as Schema from '@vegaprotocol/types'; -import * as dataHook from '../use-request-close-position-data'; -import { Requested } from './requested'; - -jest.mock('./use-request-close-position-data'); - -describe('Close position dialog - Request', () => { - const props = { - partyId: 'party-id', - order: { - marketId: 'market-id', - type: Schema.OrderType.TYPE_MARKET as const, - timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK as const, - side: Schema.Side.SIDE_BUY, - size: '10', - }, - }; - - it('loading state', async () => { - jest.spyOn(dataHook, 'useRequestClosePositionData').mockReturnValue({ - loading: false, - market: null, - marketData: null, - orders: [], - }); - render(); - expect(screen.getByText('Loading...')).toBeInTheDocument(); - }); - - it('renders message if no closing order found', async () => { - const orders = [ - { - size: '200', - price: '999', - side: Schema.Side.SIDE_BUY, - timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, - }, - { - size: '300', - price: '888', - side: Schema.Side.SIDE_SELL, - timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, - }, - ]; - jest.spyOn(dataHook, 'useRequestClosePositionData').mockReturnValue({ - market: { - decimalPlaces: 2, - positionDecimalPlaces: 2, - tradableInstrument: { - instrument: { - name: 'test market', - product: { - // @ts-ignore avoiding having to add every property on the type - settlementAsset: { - symbol: 'SYM', - }, - }, - }, - }, - }, - // @ts-ignore avoid all fields - marketData: { - markPrice: '100', - }, - // @ts-ignore avoid all fields - orders, - }); - render(); - - // closing order - const closingOrderHeader = screen.getByText('Position to be closed'); - const closingOrderTable = within( - closingOrderHeader.nextElementSibling?.querySelector( - 'tbody' - ) as HTMLElement - ); - const closingOrderRow = closingOrderTable.getAllByRole('row'); - expect(closingOrderRow[0].children[0]).toHaveTextContent('test market'); - expect(closingOrderRow[0].children[1]).toHaveTextContent('+0.1'); - expect(closingOrderRow[0].children[2]).toHaveTextContent('~1.00 SYM'); - - // orders - const ordersHeading = screen.getByText('Orders to be closed'); - const ordersTable = within( - ordersHeading.nextElementSibling?.querySelector('tbody') as HTMLElement - ); - const orderRows = ordersTable.getAllByRole('row'); - expect(orderRows).toHaveLength(orders.length); - expect(orderRows[0].children[0]).toHaveTextContent('+2'); - expect(orderRows[0].children[1]).toHaveTextContent('9.99 SYM'); - expect(orderRows[0].children[2]).toHaveTextContent( - "Good 'til Cancelled (GTC)" - ); - - expect(orderRows[1].children[0]).toHaveTextContent('-3'); - expect(orderRows[1].children[1]).toHaveTextContent('8.88 SYM'); - expect(orderRows[1].children[2]).toHaveTextContent( - "Good 'til Cancelled (GTC)" - ); - }); -}); diff --git a/libs/positions/src/lib/close-position-dialog/requested.tsx b/libs/positions/src/lib/close-position-dialog/requested.tsx deleted file mode 100644 index 8d965661b..000000000 --- a/libs/positions/src/lib/close-position-dialog/requested.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { t } from '@vegaprotocol/i18n'; -import type { ClosingOrder as IClosingOrder } from '../use-close-position'; -import { useRequestClosePositionData } from '../use-request-close-position-data'; -import { ActiveOrders, ClosingOrder } from './shared'; - -export const Requested = ({ - order, - partyId, -}: { - order?: IClosingOrder; - partyId: string; -}) => { - const { market, marketData, orders, loading } = useRequestClosePositionData( - order?.marketId, - partyId - ); - - if (loading || !market || !marketData || !orders) { - return
{t('Loading...')}
; - } - - if (!order) { - return ( -
- {t('Could not create closing order')} -
- ); - } - - return ( - <> -

{t('Position to be closed')}

- - - - ); -}; diff --git a/libs/positions/src/lib/close-position-dialog/shared.tsx b/libs/positions/src/lib/close-position-dialog/shared.tsx deleted file mode 100644 index e8d6df8aa..000000000 --- a/libs/positions/src/lib/close-position-dialog/shared.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import type { MarketData, Market } from '@vegaprotocol/market-list'; -import type { Order } from '@vegaprotocol/orders'; -import { timeInForceLabel } from '@vegaprotocol/orders'; -import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; -import { t } from '@vegaprotocol/i18n'; -import { Size } from '@vegaprotocol/react-helpers'; -import type { ReactNode } from 'react'; -import type { ClosingOrder as IClosingOrder } from '../use-close-position'; - -export const ClosingOrder = ({ - order, - market, - marketData, -}: { - order: IClosingOrder; - market: Market; - marketData: MarketData; -}) => { - const asset = market.tradableInstrument.instrument.product.settlementAsset; - const estimatedPrice = - marketData && market - ? addDecimalsFormatNumber(marketData.markPrice, market.decimalPlaces) - : '-'; - const size = market ? ( - - ) : ( - '-' - ); - - return ( - - ); -}; - -export const ActiveOrders = ({ - market, - orders, -}: { - market: Market; - orders: Order[]; -}) => { - const asset = market.tradableInstrument.instrument.product.settlementAsset; - - if (!orders.length) { - return null; - } - - return ( -
-

{t('Orders to be closed')}

- { - return [ - , - `${addDecimalsFormatNumber(o.price, market.decimalPlaces)} ${ - asset.symbol - }`, - timeInForceLabel(o.timeInForce), - ]; - })} - /> -
- ); -}; - -interface BasicTableProps { - headers: ReactNode[]; - rows: ReactNode[][]; -} - -const BasicTable = ({ headers, rows }: BasicTableProps) => { - return ( - - - - {headers.map((h, i) => ( - - ))} - - - - {rows.map((cells, i) => ( - - {cells.map((c, i) => ( - - ))} - - ))} - -
- {h} -
- {c} -
- ); -}; diff --git a/libs/positions/src/lib/positions-data-providers.ts b/libs/positions/src/lib/positions-data-providers.ts index 660a749ce..b444d0e6f 100644 --- a/libs/positions/src/lib/positions-data-providers.ts +++ b/libs/positions/src/lib/positions-data-providers.ts @@ -349,16 +349,15 @@ export const volumeAndMarginProvider = makeDerivedDataProvider< PositionsQueryVariables & MarketDataQueryVariables >( [ - (callback, client, variables) => + (callback, client, { partyId, marketId }) => ordersProvider(callback, client, { - ...variables, + partyId, + marketIds: [marketId], filter: { - order: { - status: [ - OrderStatus.STATUS_ACTIVE, - OrderStatus.STATUS_PARTIALLY_FILLED, - ], - }, + status: [ + OrderStatus.STATUS_ACTIVE, + OrderStatus.STATUS_PARTIALLY_FILLED, + ], }, }), (callback, client, variables) => diff --git a/libs/positions/src/lib/positions-manager.tsx b/libs/positions/src/lib/positions-manager.tsx index e07dccf1c..75d73da54 100644 --- a/libs/positions/src/lib/positions-manager.tsx +++ b/libs/positions/src/lib/positions-manager.tsx @@ -1,8 +1,7 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import type { Position } from '../'; import { usePositionsData, PositionsTable } from '../'; -import type { FilterChangedEvent } from 'ag-grid-community'; import type { AgGridReact } from 'ag-grid-react'; import * as Schema from '@vegaprotocol/types'; import { useVegaTransactionStore } from '@vegaprotocol/wallet'; @@ -23,8 +22,8 @@ export const PositionsManager = ({ noBottomPlaceholder, }: PositionsManagerProps) => { const gridRef = useRef(null); - const [dataCount, setDataCount] = useState(0); const { data, error, loading, reload } = usePositionsData(partyId, gridRef); + const [dataCount, setDataCount] = useState(data?.length ?? 0); const create = useVegaTransactionStore((store) => store.create); const onClose = ({ marketId, @@ -67,10 +66,7 @@ export const PositionsManager = ({ setId, disabled: noBottomPlaceholder, }); - useEffect(() => { - setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0); - }, [data]); - const onFilterChanged = useCallback((event: FilterChangedEvent) => { + const updateRowCount = useCallback(() => { setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0); }, []); return ( @@ -83,7 +79,8 @@ export const PositionsManager = ({ suppressLoadingOverlay suppressNoRowsOverlay isReadOnly={isReadOnly} - onFilterChanged={onFilterChanged} + onFilterChanged={updateRowCount} + onRowDataUpdated={updateRowCount} {...bottomPlaceholderProps} />
diff --git a/libs/positions/src/lib/positions-table.tsx b/libs/positions/src/lib/positions-table.tsx index 8732e42d9..9ac49f2fd 100644 --- a/libs/positions/src/lib/positions-table.tsx +++ b/libs/positions/src/lib/positions-table.tsx @@ -14,12 +14,12 @@ import { PriceFlashCell, signedNumberCssClass, signedNumberCssClassRules, + MarketNameCell, } from '@vegaprotocol/datagrid'; import { ButtonLink, Tooltip, TooltipCellComponent, - Link, ExternalLink, Icon, ProgressBarCell, @@ -43,7 +43,7 @@ import { useEnvironment } from '@vegaprotocol/environment'; interface Props extends TypedDataAgGrid { onClose?: (data: Position) => void; - onMarketClick?: (id: string) => void; + onMarketClick?: (id: string, metaKey?: boolean) => void; style?: CSSProperties; isReadOnly: boolean; } @@ -96,26 +96,19 @@ export const PositionsTable = forwardRef( filterParams: { buttons: ['reset'] }, tooltipComponent: TooltipCellComponent, }} - components={{ AmountCell, PriceFlashCell, ProgressBarCell }} + components={{ + AmountCell, + PriceFlashCell, + ProgressBarCell, + MarketNameCell, + }} {...props} > ) => - onMarketClick ? ( - data?.marketId && onMarketClick(data?.marketId)} - > - {value} - - ) : ( - value - ) - } + cellRenderer="MarketNameCell" + cellRendererParams={{ idPath: 'marketId', onMarketClick }} minWidth={190} /> ) { - const mockTransactionResult: MockedResponse = { - request: { - query: TransactionEventDocument, - variables: { - partyId: context?.pubKey || '', - }, - }, - result: { - data: { - busEvents: [ - { - type: Types.BusEventType.TransactionResult, - event: txResult, - __typename: 'BusEvent', - }, - ] as TransactionEventSubscription['busEvents'], - }, - }, - }; - const mockOrderResult: MockedResponse = { - request: { - query: OrderSubDocument, - variables: { - partyId: context?.pubKey || '', - }, - }, - result: { - data: { - orders: [ - { - type: Types.OrderType.TYPE_LIMIT, - id: '2fca514cebf9f465ae31ecb4c5721e3a6f5f260425ded887ca50ba15b81a5d50', - status: Types.OrderStatus.STATUS_ACTIVE, - rejectionReason: null, - createdAt: '2022-07-05T14:25:47.815283706Z', - expiresAt: '2022-07-05T14:25:47.815283706Z', - size: '10', - price: '300000', - timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_GTC, - side: Types.Side.SIDE_BUY, - marketId: 'market-id', - __typename: 'OrderUpdate', - }, - ], - }, - }, - }; - - const wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - - ); - return renderHook(() => useClosePosition(), { wrapper }); -} - -describe('useClosePosition', () => { - const txResponse = { - signature: - 'cfe592d169f87d0671dd447751036d0dddc165b9c4b65e5a5060e2bbadd1aa726d4cbe9d3c3b327bcb0bff4f83999592619a2493f9bbd251fae99ce7ce766909', - transactionHash: '0x123', - }; - - it('doesnt send the tx if there is no open volume', () => { - const mockSend = jest.fn(); - const { result } = setup({ sendTx: mockSend }); - expect(result.current).toEqual({ - submit: expect.any(Function), - transaction: initialState, - Dialog: expect.any(Function), - }); - result.current.submit({ marketId: 'test-market', openVolume: '0' }); - expect(mockSend).not.toBeCalled(); - expect(result.current.transaction.status).toEqual(VegaTxStatus.Default); - }); - - it('doesnt send the tx if there is no pubkey', () => { - const mockSend = jest.fn(); - const { result } = setup({ sendTx: mockSend, pubKey: null }); - result.current.submit({ marketId: 'test-market', openVolume: '1000' }); - expect(mockSend).not.toBeCalled(); - expect(result.current.transaction.status).toEqual(VegaTxStatus.Default); - }); - - it('closes long positions', async () => { - const marketId = 'test-market'; - const openVolume = '1000'; - const mockSend = jest.fn().mockResolvedValue(txResponse); - const { result } = setup({ sendTx: mockSend, pubKey }); - - act(() => { - result.current.submit({ marketId, openVolume }); - }); - - expect(mockSend).toBeCalledWith(defaultWalletContext.pubKey, { - batchMarketInstructions: { - cancellations: [ - { - marketId, - orderId: '', - }, - ], - submissions: [ - { - marketId, - type: Types.OrderType.TYPE_MARKET, - timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK, - side: Types.Side.SIDE_SELL, - size: openVolume, - }, - ], - }, - }); - - expect(result.current.transaction.status).toEqual(VegaTxStatus.Requested); - - await waitFor(() => { - expect(result.current.transaction).toEqual({ - status: VegaTxStatus.Complete, - signature: txResponse.signature, - txHash: txResponse.transactionHash, - dialogOpen: true, - error: null, - }); - expect(result.current.transactionResult).toEqual(txResult); - }); - }); - - it('closes short positions', async () => { - const marketId = 'test-market'; - const openVolume = '-1000'; - const mockSend = jest.fn().mockResolvedValue(txResponse); - const { result } = setup({ sendTx: mockSend, pubKey }); - - act(() => { - result.current.submit({ marketId, openVolume }); - }); - - expect(mockSend).toBeCalledWith(defaultWalletContext.pubKey, { - batchMarketInstructions: { - cancellations: [ - { - marketId, - orderId: '', - }, - ], - submissions: [ - { - marketId, - type: Types.OrderType.TYPE_MARKET, - timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK, - side: Types.Side.SIDE_BUY, - size: openVolume.replace('-', ''), - }, - ], - }, - }); - - expect(result.current.transaction.status).toEqual(VegaTxStatus.Requested); - - await waitFor(() => { - expect(result.current.transaction).toEqual({ - status: VegaTxStatus.Complete, - signature: txResponse.signature, - txHash: txResponse.transactionHash, - dialogOpen: true, - error: null, - }); - expect(result.current.transactionResult).toEqual(txResult); - }); - }); -}); diff --git a/libs/positions/src/lib/use-close-position.ts b/libs/positions/src/lib/use-close-position.ts deleted file mode 100644 index 5c767d0b2..000000000 --- a/libs/positions/src/lib/use-close-position.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { useCallback, useState } from 'react'; -import type { TransactionResult } from '@vegaprotocol/wallet'; -import { determineId } from '@vegaprotocol/wallet'; -import { useVegaWallet, useTransactionResult } from '@vegaprotocol/wallet'; -import { useVegaTransaction } from '@vegaprotocol/wallet'; -import * as Sentry from '@sentry/react'; -import * as Schema from '@vegaprotocol/types'; -import { useOrderUpdate } from '@vegaprotocol/orders'; -import type { OrderSubFieldsFragment } from '@vegaprotocol/orders'; - -export interface ClosingOrder { - marketId: string; - type: Schema.OrderType.TYPE_MARKET; - timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK; - side: Schema.Side; - size: string; -} - -export const useClosePosition = () => { - const { pubKey } = useVegaWallet(); - const { send, transaction, setComplete, Dialog } = useVegaTransaction(); - const [closingOrder, setClosingOrder] = useState(); - const [closingOrderResult, setClosingOrderResult] = - useState(); - const [transactionResult, setTransactionResult] = - useState(); - const waitForTransactionResult = useTransactionResult(); - const waitForOrder = useOrderUpdate(transaction); - - const submit = useCallback( - async ({ - marketId, - openVolume, - }: { - marketId: string; - openVolume: string; - }) => { - if (!pubKey || openVolume === '0') { - return; - } - - setTransactionResult(undefined); - setClosingOrder(undefined); - - try { - // figure out if position is long or short and make side the opposite - const side = openVolume.startsWith('-') - ? Schema.Side.SIDE_BUY - : Schema.Side.SIDE_SELL; - - // volume could be prefixed with '-' if position is short, remove it - const size = openVolume.replace('-', ''); - const closingOrder = { - marketId: marketId, - type: Schema.OrderType.TYPE_MARKET as const, - timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK as const, - side, - size, - }; - - setClosingOrder(closingOrder); - - const command = { - batchMarketInstructions: { - cancellations: [ - { - marketId, - orderId: '', // omit order id to cancel all active orders - }, - ], - submissions: [closingOrder], - }, - }; - - const res = await send(pubKey, command); - - if (res) { - const orderId = determineId(res.signature); - const [txResult, orderResult] = await Promise.all([ - waitForTransactionResult(res.transactionHash, pubKey), - waitForOrder(orderId, pubKey), - ]); - setTransactionResult(txResult); - setClosingOrderResult(orderResult); - setComplete(); - } - - return res; - } catch (e) { - Sentry.captureException(e); - return; - } - }, - [pubKey, send, setComplete, waitForTransactionResult, waitForOrder] - ); - - return { - transaction, - transactionResult, - submit, - closingOrder, - closingOrderResult, - Dialog, - }; -}; diff --git a/libs/positions/src/lib/use-request-close-position-data.ts b/libs/positions/src/lib/use-request-close-position-data.ts deleted file mode 100644 index 8d4414334..000000000 --- a/libs/positions/src/lib/use-request-close-position-data.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list'; -import { isOrderActive, ordersWithMarketProvider } from '@vegaprotocol/orders'; -import type { OrdersQueryVariables } from '@vegaprotocol/orders'; -import { useDataProvider } from '@vegaprotocol/react-helpers'; -import { useMemo } from 'react'; - -export const useRequestClosePositionData = ( - marketId?: string, - partyId?: string -) => { - const marketVariables = useMemo( - () => ({ marketId: marketId || '' }), - [marketId] - ); - const orderVariables = useMemo( - () => ({ partyId: partyId || '' }), - [partyId] - ); - const { data: market, loading: marketLoading } = useDataProvider({ - dataProvider: marketProvider, - variables: marketVariables, - skip: !marketId, - }); - const { data: marketData, loading: marketDataLoading } = useDataProvider({ - dataProvider: marketDataProvider, - variables: marketVariables, - }); - const { data: orderData, loading: orderDataLoading } = useDataProvider({ - dataProvider: ordersWithMarketProvider, - variables: orderVariables, - skip: !partyId, - }); - - const orders = useMemo(() => { - if (!orderData || !market) return []; - return ( - orderData - .filter((o) => { - // Filter out orders not on market for position - if ( - !o || - !o.node || - !o.node.market || - o.node.market.id !== market.id - ) { - return false; - } - - if (!isOrderActive(o.node.status)) { - return false; - } - - return true; - }) - // @ts-ignore o is never null as its been filtered out above - .map((o) => o.node) - ); - }, [orderData, market]); - - return { - market, - marketData, - orders, - loading: marketLoading || marketDataLoading || orderDataLoading, - }; -}; diff --git a/libs/types/src/global-types-mappings.ts b/libs/types/src/global-types-mappings.ts index 15a225c0b..eaf84b1e0 100644 --- a/libs/types/src/global-types-mappings.ts +++ b/libs/types/src/global-types-mappings.ts @@ -1,3 +1,4 @@ +import type { ConditionOperator } from './__generated__/types'; import type { AccountType, AuctionTrigger, @@ -458,3 +459,11 @@ export const PositionStatusMapping: { POSITION_STATUS_ORDERS_CLOSED: 'Maintained by network', POSITION_STATUS_UNSPECIFIED: 'Normal', }; + +export const ConditionOperatorMapping: { [C in ConditionOperator]: string } = { + OPERATOR_EQUALS: 'Equals', + OPERATOR_GREATER_THAN: 'Greater than', + OPERATOR_GREATER_THAN_OR_EQUAL: 'Greater than or equal to', + OPERATOR_LESS_THAN: 'Less than', + OPERATOR_LESS_THAN_OR_EQUAL: 'Less than or equal to', +}; diff --git a/libs/ui-toolkit/src/components/checkbox/checkbox.tsx b/libs/ui-toolkit/src/components/checkbox/checkbox.tsx index fed4c2aa1..78994b6b2 100644 --- a/libs/ui-toolkit/src/components/checkbox/checkbox.tsx +++ b/libs/ui-toolkit/src/components/checkbox/checkbox.tsx @@ -38,6 +38,7 @@ export const Checkbox = ({ checked={checked} onCheckedChange={onCheckedChange} disabled={disabled} + data-testid={name} > {checked === 'indeterminate' ? ( @@ -54,7 +55,12 @@ export const Checkbox = ({ )} -
diff --git a/libs/utils/src/lib/generic-data-provider.ts b/libs/utils/src/lib/generic-data-provider.ts index 7a14f1b4c..2dae214a5 100644 --- a/libs/utils/src/lib/generic-data-provider.ts +++ b/libs/utils/src/lib/generic-data-provider.ts @@ -339,7 +339,7 @@ function makeDataProviderInternal< } }; - const initialFetch = async () => { + const initialFetch = async (isUpdate = false) => { if (!client) { return; } @@ -394,7 +394,7 @@ function makeDataProviderInternal< subscription = undefined; } finally { loading = false; - notifyAll(); + notifyAll({ isUpdate }); } }; @@ -410,7 +410,7 @@ function makeDataProviderInternal< } else { loading = true; error = undefined; - initialFetch(); + initialFetch(true); } }; diff --git a/libs/wallet/src/connectors/vega-connector.ts b/libs/wallet/src/connectors/vega-connector.ts index 0199e4aea..27327a916 100644 --- a/libs/wallet/src/connectors/vega-connector.ts +++ b/libs/wallet/src/connectors/vega-connector.ts @@ -45,6 +45,7 @@ export interface OrderSubmission { size: string; price?: string; expiresAt?: string; + postOnly?: boolean; reduceOnly?: boolean; } diff --git a/libs/wallet/src/utils.ts b/libs/wallet/src/utils.ts index 2c3ccde83..292a211c1 100644 --- a/libs/wallet/src/utils.ts +++ b/libs/wallet/src/utils.ts @@ -48,6 +48,8 @@ export const normalizeOrderSubmission = ( order.expiresAt && order.timeInForce === OrderTimeInForce.TIME_IN_FORCE_GTT ? toNanoSeconds(order.expiresAt) : undefined, + postOnly: order.postOnly, + reduceOnly: order.reduceOnly, }); export const normalizeOrderAmendment = >( diff --git a/package.json b/package.json index c34a16de7..39212a980 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "build:all": "nx run-many --all --target=build", "lint:all": "nx run-many --all --target=lint", "e2e:all": "nx run-many --all --target=e2e", - "vegacapsule": "vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl" + "vegacapsule": "vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl", + "release": "git checkout develop ; git pull ; node scripts/make-release.js" }, "engines": { "node": ">=16.15.1" @@ -166,6 +167,7 @@ "flush-promises": "^1.0.2", "glob": "^8.0.3", "husky": "^7.0.4", + "inquirer": "^8.0.0", "jest": "27.5.1", "jest-canvas-mock": "^2.3.1", "jest-websocket-mock": "^2.3.0", diff --git a/scripts/make-release.js b/scripts/make-release.js new file mode 100644 index 000000000..724226da3 --- /dev/null +++ b/scripts/make-release.js @@ -0,0 +1,82 @@ +const { execSync } = require('child_process'); +const inquirer = require('inquirer'); + +const getTags = (take) => { + const tags = execSync( + "git for-each-ref --sort=taggerdate --format '%(refname)' refs/tags" + ) + .toString() + .trim() + .split('\n') + .map((t) => t.replace('refs/tags/', '')) + .reverse() + .slice(0, take); + return tags; +}; + +const getReleaseBranches = () => { + const branches = execSync('git branch --remote | grep origin/release/') + .toString() + .trim() + .split('\n') + .map((b) => b.trim().replace('origin/release/', '')); + return branches; +}; + +const release = (tag, branch) => { + const steps = [ + `git checkout ${branch}`, + 'git pull', + `git reset --hard ${tag}`, + 'git push --force', + ]; + try { + for (const cmd of steps) { + const result = execSync(cmd).toString(); + } + } catch (err) { + console.error('Could not make a release'); + } +}; + +inquirer + .prompt([ + { + type: 'list', + name: 'tag', + message: 'What version would you like to release?', + choices: getTags(), + }, + { + type: 'checkbox', + name: 'envs', + message: 'To what environment you wish to release?', + choices: ['mainnet', ...getReleaseBranches()], + validate(answer) { + return answer.length > 0; + }, + }, + ]) + .then((answers) => { + inquirer + .prompt({ + type: 'confirm', + name: 'sure', + message: `Are you sure? This will release ${ + answers.tag + } to ${answers.envs.join(', ')}`, + }) + .then(() => { + for (const env of answers.envs) { + const branch = env === 'mainnet' ? 'master' : `release/${env}`; + release(answers.tag, branch); + } + execSync('git checkout develop'); + }) + .catch((err) => { + console.log('Something went wrong', err.toString()); + }); + }) + .catch((err) => { + console.log('Something went wrong', err.toString()); + }); diff --git a/tsconfig.base.json b/tsconfig.base.json index 1c649e50e..c0e01c8ce 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -36,6 +36,7 @@ "@vegaprotocol/mock": ["libs/cypress/mock.ts"], "@vegaprotocol/network-info": ["libs/network-info/src/index.ts"], "@vegaprotocol/network-stats": ["libs/network-stats/src/index.ts"], + "@vegaprotocol/oracles": ["libs/oracles/src/index.ts"], "@vegaprotocol/orders": ["libs/orders/src/index.ts"], "@vegaprotocol/positions": ["libs/positions/src/index.ts"], "@vegaprotocol/proposals": ["libs/proposals/src/index.ts"], diff --git a/workspace.json b/workspace.json index a3396fd74..987074145 100644 --- a/workspace.json +++ b/workspace.json @@ -26,6 +26,7 @@ "multisig-signer": "apps/multisig-signer", "network-info": "libs/network-info", "network-stats": "libs/network-stats", + "oracles": "libs/oracles", "orders": "libs/orders", "positions": "libs/positions", "proposals": "libs/proposals",