Compare commits

...
Author SHA1 Message Date
Artur Siaznik aa7c510e44 chore: refactored
fix: typo
2023-04-17 13:06:08 +02:00
Artur Siaznik 48b1ed8ca6 feat(environment): github releases 2023-04-17 13:06:08 +02:00
Mikołaj Młodzikowski d671e51edf fix(ci): adjust timeouts 2023-04-17 12:45:30 +02:00
Mikołaj Młodzikowski 4f3295d3fc feat(ci): rebuild yarn on failed build 2023-04-17 11:24:53 +02:00
dexturr 7be7bab1ca chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-17 06:09:49 +00:00
dexturr 61380f06b9 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-17 00:14:49 +00:00
dexturr c113856d63 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-16 18:07:16 +00:00
dexturr 90884beee8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-16 12:07:18 +00:00
dexturr d9d2e428f4 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-16 06:09:55 +00:00
dexturr ba8d54b761 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-16 00:15:24 +00:00
dexturr dce4edb943 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-15 18:06:56 +00:00
dexturr b468a0fd84 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-15 12:07:02 +00:00
dexturr c98fbe7453 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-15 06:06:58 +00:00
dexturr d20928ec06 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-15 00:15:18 +00:00
dexturr 289559fe4f chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-14 18:09:58 +00:00
Mikołaj Młodzikowski 071fd6128c feat: fix releasing dist 2023-04-14 15:45:14 +02:00
Mikołaj Młodzikowski 554d07a31b feat(ci): fix regressions detected after merging to develop (#3442) 2023-04-14 15:00:20 +02:00
dexturr 48b62380b0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-14 12:10:09 +00:00
Mikołaj Młodzikowski a9f4604bad feat(governance,trading,explorer): pipelines improvements (#3422) 2023-04-14 11:47:41 +02:00
Sam Keen 54f2519a62 feat(governance): toggles design review tweaks (#3431) 2023-04-14 09:22:22 +01:00
dexturr 260e7bf9cf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-14 06:09:41 +00:00
dexturr 83db796cdb chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-14 00:14:08 +00:00
m.ray 2f6800212e fix(trading): fix order toast intent on amendments (#3433) 2023-04-13 13:20:28 -07:00
dexturr 77e16ad1b7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-13 18:09:10 +00:00
dexturr dcce983842 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-13 12:10:33 +00:00
Maciek 53ce9bfaa2 chore(assets): make query for asset list lighter (#3425) 2023-04-13 11:42:09 +00:00
Edd 6ae34d296f fix(explorer): tweak footer to show more detail on smaller screens (#3418) 2023-04-13 10:49:42 +01:00
Joe Tsang ee2aafb99c fix(governance): failing nightly tests (#3351) 2023-04-13 10:46:01 +01:00
dexturr e8fdb63323 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-13 06:09:41 +00:00
dexturr 3df9270b57 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-13 00:13:51 +00:00
dexturr ab26667265 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-12 18:08:22 +00:00
m.ray 9fc6b255c8 fix(orders): update order toast intent and title (#3424) 2023-04-12 15:19:15 +00:00
dexturr 91f61d8bb5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-12 12:09:32 +00:00
Bartłomiej Głowniaandm.ray bbfda65bcc feat(withdraws): improve ux surrounding withdraw balances thresholds and delays (#3402)
Co-authored-by: m.ray <16125548+MadalinaRaicu@users.noreply.github.com>
2023-04-12 11:21:24 +02:00
Mikołaj Młodzikowski 45f2e926c3 feat(governance,trading,explorer): split steps by logic (#3387) 2023-04-12 09:38:26 +02:00
dexturr ccadbc2cb6 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-12 06:08:40 +00:00
dexturr 7128acb7b8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-12 00:14:18 +00:00
dexturr 357bdaa4f3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-11 18:07:51 +00:00
Maciek f3fe43724e fix(deposits): deposit dialog doesn't respect given assetId (#3414) 2023-04-11 16:41:20 +02:00
m.ray 03f5ca3096 fix(trading): fix formatting on clicking on orderbook cell after decimal resolution update (#3409) 2023-04-11 12:23:43 +00:00
dexturr 03d19349af chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-11 12:08:24 +00:00
daro-maj 1f53b2cbb5 test(trading): increase coverage for the remaining ACs (#3412) 2023-04-11 11:59:21 +02:00
Maciek b010c98346 fix(trading): welcome dialog with markets list click on market close window (#3404) 2023-04-11 11:34:44 +02:00
Matthew Russell 29d57af2ba fix(trading): make light theme sell volume bar match candle color (#3405) 2023-04-11 09:06:35 +02:00
Bartłomiej Głownia fd7940c4cf chore(deal-ticket): remove not used code (#3406) 2023-04-11 08:13:41 +02:00
dexturr 8ab7ac8414 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-11 06:07:03 +00:00
dexturr ce7cd03c3e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-11 00:15:30 +00:00
Matthew Russell dfa3355a68 chore(trading): prevent node poll if query fails (#3411) 2023-04-10 14:48:03 -07:00
dexturr 8553904d81 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-10 18:07:16 +00:00
dexturr 10afa3c1d4 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-10 12:10:31 +00:00
Maciek ecd362615e chore(deposits): impove UX on deposit dialog (#3401) 2023-04-10 12:59:02 +01:00
dexturr 0d9bd67465 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-10 06:07:18 +00:00
dexturr b5aa92f007 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-10 00:14:34 +00:00
dexturr 2955790be1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-09 18:09:21 +00:00
dexturr 89ff9309e9 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-09 12:08:56 +00:00
dexturr e1df3489f6 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-09 06:09:36 +00:00
dexturr f0fa9ca5e1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-09 00:17:04 +00:00
dexturr 9d92477c2a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-08 18:08:30 +00:00
dexturr b1a198be85 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-08 12:09:49 +00:00
dexturr 5fb385d43c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-08 06:07:03 +00:00
dexturr 8014b279fe chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-08 00:14:19 +00:00
dexturr f4bde57294 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-07 18:09:32 +00:00
John Walley 2e5ff07824 chore(candles-chart): upgrade pennant to v1.8.1 (#3399) 2023-04-07 10:35:10 -07:00
Bartłomiej Głownia 7accb42793 fix(market-depth): make market data up to date, use fixed number of decimals in cumulative vol (#3361) 2023-04-07 10:34:51 -07:00
Matthew Russell 9229e7f686 chore(trading): update approval amount to be max uint256 (#3395) 2023-04-07 10:33:53 -07:00
daro-maj b570b9c4c1 test(trading): tests for post and reduce functionality (#3400) 2023-04-07 13:03:15 -04:00
m.ray ed78261aad fix(trading): order tx toasts title and intent should be set by status (#3368) 2023-04-07 13:01:12 -04:00
m.ray 363ca9c6e1 fix(trading): add liquidity provision docs (#3403) 2023-04-07 13:00:57 -04:00
m.ray bafd3c384c feat(trading): include fees in transfer amount (#3345) 2023-04-07 13:00:11 -04:00
dexturr 3b830abe07 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-07 12:09:01 +00:00
dexturr 1787e22b69 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-07 06:07:23 +00:00
dexturr f105133959 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-07 00:14:21 +00:00
dexturr aff0e46d23 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-06 18:08:45 +00:00
Maciek 7e4aafcb77 chore(trading): add filters to the proposals table (#3375) 2023-04-06 15:30:21 +02:00
dexturr 643d5408cd chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-06 12:08:03 +00:00
m.ray 2a2af3ba9c fix(trading): fix docs resources (#3382) 2023-04-06 12:53:08 +01:00
Sam Keen 8d4e4a1228 fix(governance): fix incorrect validator penalties (#3381) 2023-04-06 13:31:21 +02:00
dexturr 84068c8081 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-06 06:09:30 +00:00
dexturr e1185b9a96 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-06 00:13:22 +00:00
dexturr f390448c24 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-05 18:09:37 +00:00
Maciek 70943c523c chore(deposits): persistence of deposit form state (#3348) 2023-04-05 14:53:55 +02:00
dexturr dcb79e70d3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-05 12:10:10 +00:00
dexturr cc72cdbe16 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-05 06:08:13 +00:00
dexturr 3fd1817e1e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-05 00:15:53 +00:00
Matthew Russell b483c13f81 chore(trading): fix bad grammar on approval notification (#3354) 2023-04-04 15:14:36 -07:00
Matthew Russell bde7a9fbf9 fix(smart-contracts): add increased gas limit estimation (#3371) 2023-04-04 11:41:11 -07:00
dexturr 5fd93ee9c8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-04 18:09:38 +00:00
botond 9ac07fd98a fix: add missing announcement config url (#3367) 2023-04-04 15:11:48 +00:00
dexturr 9c975ed822 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-04 12:10:23 +00:00
Edd 138bf7da00 build(explorer): update validator testnet be url (#3357) 2023-04-04 10:53:47 +00:00
dexturr 3e7fe517f3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-04 06:07:34 +00:00
dexturr 4f960d09b3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-04 00:15:07 +00:00
Bartłomiej Głownia 351a5aaf96 chore(trading): remove not used close position code and liquidity subscription (#3350) 2023-04-03 14:58:06 -07:00
Bartłomiej Głownia 381d9011a0 fix(orders): restore filter by marketId (#3342) 2023-04-03 14:20:31 -07:00
Matthew Russell 861760b4f9 chore(governance): add hardcoded token vesting contract address (#3352) 2023-04-03 21:17:32 +00:00
Matthew Russell 18f1c0014c chore(governance,explorer,multisig-signer): delete rest of sandbox/mirror env files (#3310) 2023-04-03 14:07:43 -07:00
m.ray 474543d91b fix(trading): order stopped intent toasts (#3347) 2023-04-03 19:21:56 +00:00
dexturr 662753c74b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-03 18:09:46 +00:00
daro-majandm.ray 71aa8882bc test(trading): renders correctly liquidity in trading tab (#3343)
Co-authored-by: m.ray <16125548+MadalinaRaicu@users.noreply.github.com>
2023-04-03 14:33:10 +00:00
Mikołaj Młodzikowski ecfbccf8ed feat(trading,governance,explorer): dist on s3 (#3332) 2023-04-03 15:53:50 +02:00
Bartłomiej Głownia 2aad6b1a14 feat(deal-ticket): show total margin available for an order, and where it will come from (#3318) 2023-04-03 15:17:23 +02:00
Bartłomiej Głownia 8463d371ad chore(trading): liquidity table DRY (#3346) 2023-04-03 15:02:43 +02:00
dexturr 0914e7ce4b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-03 12:08:48 +00:00
181 changed files with 46302 additions and 5052 deletions
+1 -1
View File
@@ -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: |
+190
View File
@@ -0,0 +1,190 @@
name: CI/CD
on:
push:
branches:
- release/*
- develop
pull_request:
types:
- opened
- ready_for_review
- reopened
- edited
- synchronize
jobs:
node-modules:
runs-on: ubuntu-22.04
name: 'Cache yarn modules'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache node modules
id: cache
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
# comment out "resotre-keys" if you need to rebuild yarn from 0
restore-keys: |
${{ runner.os }}-cache-node-modules-
- name: Setup node
uses: actions/setup-node@v3
if: steps.cache.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
- name: yarn install
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --pure-lockfile
lint-pr-title:
needs: node-modules
if: ${{ github.event_name == 'pull_request' }}
name: Verify PR title
uses: ./.github/workflows/lint-pr.yml
secrets: inherit
lint-test-build:
timeout-minutes: 20
needs: node-modules
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: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- 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 || (yarn install && 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 }}
dist-check:
runs-on: ubuntu-latest
needs: publish-dist
if: ${{ github.event_name == 'pull_request' }}
name: '(CD) comment preview links'
steps:
- name: Find Comment
uses: peter-evans/find-comment@v2
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Previews
- name: Inject slug/short variables
if: ${{ steps.fc.outputs.comment-id == 0 }}
uses: rlespinasse/github-slug-action@v4
with:
prefix: CI_
- name: Create comment
if: ${{ steps.fc.outputs.comment-id == 0 }}
uses: peter-evans/create-or-update-comment@v3
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
Previews
- explorer https://explorer.${{ env.CI_GITHUB_REF_NAME }}.vega.rocks
- trading https://trading.${{ env.CI_GITHUB_REF_NAME }}.vega.rocks
- governance https://governance.${{ env.CI_GITHUB_REF_NAME }}.vega.rocks
cypress-check:
name: '(CI) cypress - check'
runs-on: ubuntu-latest
needs: cypress
steps:
- run: echo Done!
# 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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -1,4 +1,4 @@
name: Cypress Run
name: (CI) Cypress Run
on:
workflow_call:
inputs:
+12 -9
View File
@@ -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
+29
View File
@@ -0,0 +1,29 @@
---
name: Verify PR title
on:
workflow_call:
jobs:
lint_pr:
timeout-minutes: 10
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- 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: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
-23
View File
@@ -1,23 +0,0 @@
---
name: Verify PR title
on:
pull_request:
types: [opened, ready_for_review, reopened, edited, synchronize]
jobs:
lint_pr:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.15.1
- name: Install root dependencies
run: yarn install
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
-97
View File
@@ -1,97 +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
uses: actions/checkout@v3
with:
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 }}
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v3
with:
main-branch-name: develop
- 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: See affected apps
run: |
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
+1 -1
View File
@@ -7,7 +7,7 @@ on:
jobs:
master:
name: Generate Queries
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
+171
View File
@@ -0,0 +1,171 @@
name: (CD) Publish docker + s3
on:
workflow_call:
inputs:
projects:
required: true
type: string
jobs:
publish-dist:
strategy:
fail-fast: false
matrix:
app: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.app }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up QEMU
id: quemu
uses: docker/setup-qemu-action@v2
- name: Available platforms
run: echo ${{ steps.qemu.outputs.platforms }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- 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: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
# https://docs.github.com/en/actions/learn-github-actions/contexts
- name: Define variables
run: |
envName=''
dockerfile="dist.Dockerfile"
if [[ "${{ github.event_name }}" = "push" ]]; then
domain="vega.rocks"
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
if [[ "${{ github.ref }}" =~ .*mainnet.* ]]; then
domain="vega.community"
if [[ "${{ matrix.app }}" = "trading" ]]; then
dockerfile="ipfs.Dockerfile"
fi
fi
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
envName="stagnet3"
fi
bucketName="${{ matrix.app }}.${envName}.${domain}"
echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV
fi
nodeVersion=$(cat .nvmrc | head -n 1)
echo ENV_NAME=${envName} >> $GITHUB_ENV
echo NODE_VERSION=${nodeVersion} >> $GITHUB_ENV
echo DOCKERFILE=docker/${dockerfile} >> $GITHUB_ENV
- name: Build local dist
if: ${{ env.DOCKERFILE != 'docker/ipfs.Dockerfile' }}
run: |
flags=""
if [[ ! -z "${{ env.ENV_NAME }}" ]]; then
if [[ "${{ env.ENV_NAME }}" != "ops-vega" ]]; then
flags="--env=${{ env.ENV_NAME }}"
fi
fi
if [ "${{ matrix.app }}" = "trading" ]; then
yarn nx export trading $flags || (yarn install && yarn nx export trading $flags)
DIST_LOCATION=dist/apps/trading/exported
else
yarn nx build ${{ matrix.app }} $flags || (yarn install && yarn nx build ${{ matrix.app }} $flags)
DIST_LOCATION=dist/apps/${{ matrix.app }}
fi
mv $DIST_LOCATION dist-result
tree dist-result
- name: Build and export to local Docker
id: docker_build
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
uses: docker/build-push-action@v3
with:
context: .
file: ${{ env.DOCKERFILE }}
load: true
build-args: |
APP=${{ matrix.app }}
NODE_VERSION=${{ env.NODE_VERSION }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Image digest
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
run: echo ${{ steps.docker_build.outputs.digest }}
- name: Sanity check docker image
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
run: |
echo "Check ipfs-hash"
if [[ "${{ env.DOCKERFILE }}" = "docker/ipfs.Dockerfile" ]]; then
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash
fi
echo "List html directory"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree .'
- name: Copy dist to local filesystem
if: ${{ env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' }}
run: |
docker create --name=dist ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
docker cp dist:/usr/share/nginx/html dist
echo "check local dist files"
tree dist/html
mv dist/html dist-result
- name: Publish dist as docker image
uses: docker/build-push-action@v3
if: ${{ github.event_name == 'pull_request' }}
with:
context: .
file: ${{ env.DOCKERFILE }}
push: true
build-args: |
APP=${{ matrix.app }}
NODE_VERSION=${{ env.NODE_VERSION }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }}
# bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3
uses: jakejarvis/s3-sync-action@master
if: ${{ github.event_name == 'push' }}
with:
args: --acl private --follow-symlinks --delete
env:
AWS_S3_BUCKET: ${{ env.BUCKET_NAME }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'eu-west-1'
SOURCE_DIR: 'dist-result'
- name: Add preview label
uses: actions-ecosystem/action-add-labels@v1
if: ${{ github.event_name == 'pull_request' }}
with:
labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }}
@@ -1,94 +0,0 @@
name: Docker build
on:
workflow_call:
inputs:
projects:
required: true
type: string
jobs:
master:
strategy:
fail-fast: false
matrix:
app: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.app }}
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up QEMU
id: quemu
uses: docker/setup-qemu-action@v2
- name: Available platforms
run: echo ${{ steps.qemu.outputs.platforms }}
- 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:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v3
with:
push: true
build-args: |
APP=${{ matrix.app }}
NODE_VERSION=${{ steps.tags.outputs.npmVersion }}
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:latest
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ steps.tags.outputs.version }}
- name: Add preview label
uses: actions-ecosystem/action-add-labels@v1
if: ${{ github.event_name == 'pull_request' }}
with:
labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }}
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
+9 -11
View File
@@ -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:
-46
View File
@@ -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
+1 -1
View File
@@ -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
-20
View File
@@ -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
-12
View File
@@ -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
+1 -1
View File
@@ -9,5 +9,5 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
@@ -10,7 +10,7 @@ export const Footer = () => {
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
const { screenSize } = useScreenDimensions();
const showFullFeedbackLabel = useMemo(
() => ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
() => ['md', 'lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
[screenSize]
);
@@ -44,7 +44,8 @@ describe(
function () {
before('connect wallets and set approval limit', function () {
cy.visit('/');
vegaWalletSetSpecifiedApprovalAmount('1000');
ethereumWalletConnect();
cy.associateTokensToVegaWallet('1');
});
beforeEach('visit proposals tab', function () {
@@ -213,6 +214,7 @@ describe(
// 3001-VOTE-042, 3001-VOTE-057, 3001-VOTE-058, 3001-VOTE-059, 3001-VOTE-060
it('Newly created proposal details - ability to increase associated tokens - by voting again after association', function () {
vegaWalletSetSpecifiedApprovalAmount('1000');
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getSubmittedProposalFromProposalList(rawProposal.rationale.title)
@@ -13,7 +13,6 @@ import {
createUpdateNetworkProposalTxBody,
createFreeFormProposalTxBody,
} from '../../support/proposal.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions';
@@ -33,10 +32,6 @@ context(
before('Connect wallets and set approval', function () {
cy.visit('/');
vegaWalletSetSpecifiedApprovalAmount('1000');
cy.connectVegaWallet();
ethereumWalletConnect();
ensureSpecifiedUnstakedTokensAreAssociated('1');
cy.clearLocalStorage();
});
beforeEach('visit proposals', function () {
@@ -114,7 +109,7 @@ context(
navigateTo(navigation.proposals);
cy.reload();
waitForSpinner();
cy.get(openProposals).within(() => {
cy.get(openProposals, { timeout: 6000 }).within(() => {
cy.contains(proposalTitle)
.parentsUntil('[data-testid="proposals-list-item"]')
.within(() => cy.get(viewProposalButton).click());
@@ -232,7 +232,7 @@ context(
it('Unable to create a freeform proposal - when json parent section contains unexpected field', function () {
const errorMsg =
'Invalid params: the transaction does not use a valid Vega command: unknown field unexpected" in vega.commands.v1.ProposalSubmission';
'Invalid params: the transaction does not use a valid Vega command: unknown field "unexpected" in vega.commands.v1.ProposalSubmission';
// 3001-VOTE-038 3002-PROP-013 3002-PROP-014
goToMakeNewProposal(governanceProposalType.RAW);
@@ -313,7 +313,7 @@ context(
it('Unable to vote on a proposal - when vega wallet disconnected - option to connect from within', function () {
createRawProposal();
cy.get('[data-testid="manage-vega-wallet"]').click();
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
cy.get('[data-testid="disconnect"]').click();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getSubmittedProposalFromProposalList(
@@ -218,7 +218,7 @@ context(
it('Unable to submit new market proposal with missing/invalid fields', function () {
const errorMsg =
'Invalid params: the transaction is not a valid Vega command: unknown field "filters" in vega.DataSourceDefinition';
'Invalid params: the transaction does not use a valid Vega command: unknown field "invalid" in vega.NewMarket';
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
cy.get(newProposalSubmitButton).should('be.visible').click();
@@ -436,7 +436,7 @@ context(
});
});
it.only('Able to submit update asset proposal using max deadline', function () {
it('Able to submit update asset proposal using max deadline', function () {
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
enterUpdateAssetProposalDetails();
cy.get(maxVoteDeadline).click();
@@ -25,11 +25,11 @@ const vegaWalletUnstakedBalance =
'[data-testid="vega-wallet-balance-unstaked"]';
const txTimeout = Cypress.env('txTimeout');
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
const ethWalletAssociateButton = '[data-testid="associate-btn"]';
const ethWalletAssociateButton = '[data-testid="associate-btn"]:visible';
const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]';
const tokenAmountInputBox = '[data-testid="token-amount-input"]';
const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
const ethWalletDissociateButton = '[href="/token/disassociate"]';
const ethWalletDissociateButton = '[href="/token/disassociate"]:visible';
const vestingContractSection = '[data-testid="vega-in-vesting-contract"]';
const vegaInWalletSection = '[data-testid="vega-in-wallet"]';
const connectedVegaKey = '[data-testid="connected-vega-key"]';
@@ -78,12 +78,12 @@ context(
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
6
);
validateWalletCurrency('Associated', '0.00');
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '2.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
// 0005-ETXN-002
verifyEthWalletAssociatedBalance('2.0');
@@ -111,12 +111,12 @@ context(
stakingPageDisassociateTokens('2');
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
6
);
validateWalletCurrency('Associated', '2.00');
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '0.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
cy.getByTestId('eth-wallet-associated-balances', txTimeout).should(
'not.exist'
);
@@ -192,12 +192,12 @@ context(
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
6
);
validateWalletCurrency('Associated', '0.00');
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '2.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
verifyEthWalletAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('2.0');
cy.get(vegaWallet).within(() => {
@@ -210,12 +210,12 @@ context(
});
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
6
);
validateWalletCurrency('Associated', '2.00');
validateWalletCurrency('Pending association', '1.00');
validateWalletCurrency('Total associated after pending', '1.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
verifyEthWalletAssociatedBalance('1.0');
verifyEthWalletTotalAssociatedBalance('1.0');
});
@@ -266,7 +266,7 @@ context(
// 1004-ASSO-008
// 1004-ASSO-010
// No warning visible as described in AC, but the button is disabled
cy.get(ethWalletAssociateButton).first().click();
cy.get(ethWalletAssociateButton).click();
cy.get(associateWalletRadioButton, { timeout: 30000 }).click();
cy.get(tokenSubmitButton, txTimeout).should('be.disabled'); // button disabled with no input
cy.get(tokenAmountInputBox, { timeout: 10000 }).type('6500000');
@@ -278,12 +278,12 @@ context(
vegaWalletAssociate('2');
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
6
);
validateWalletCurrency('Associated', '0.00');
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '2.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
validateWalletCurrency('Associated', '2.00');
});
@@ -294,24 +294,24 @@ context(
});
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
6
);
validateWalletCurrency('Associated', '2.00');
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '0.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
validateWalletCurrency('Associated', '0.00');
});
it('Able to associate tokens to different public key of connected vega wallet', function () {
cy.get(ethWalletAssociateButton).first().click();
cy.get(ethWalletAssociateButton).click();
cy.get(associateWalletRadioButton).click();
cy.get(connectedVegaKey).should(
'have.text',
Cypress.env('vegaWalletPublicKey')
);
cy.get('[data-testid="manage-vega-wallet"]').click();
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
cy.get(connectedVegaKey).should(
'have.text',
@@ -166,6 +166,7 @@ export function goToMakeNewProposal(proposalType: string) {
navigateTo(navigation.proposals);
cy.get(newProposalButton).should('be.visible').click();
cy.url().should('include', '/proposals/propose');
cy.get(navigation.pageSpinner, { timeout: 20000 }).should('not.exist');
cy.get('li').should('contain.text', proposalType).and('be.visible');
cy.get('li').contains(proposalType).click();
}
@@ -8,6 +8,7 @@ import {
} from '@vegaprotocol/smart-contracts';
import { ethers, Wallet } from 'ethers';
const associatedAmountInWallet = '[data-testid="associated-amount"]:visible';
const vegaWalletContainer = 'aside [data-testid="vega-wallet"]';
const vegaWalletMnemonic = Cypress.env('vegaWalletMnemonic');
const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey');
@@ -59,7 +60,7 @@ export async function faucetAsset(assetEthAddress: string) {
}
export async function vegaWalletTeardown() {
cy.get('[data-testid="associated-amount"]')
cy.get(associatedAmountInWallet)
.should('be.visible')
.invoke('text')
.then((associatedAmount) => {
@@ -68,12 +69,12 @@ export async function vegaWalletTeardown() {
$body.find('[data-testid="eth-wallet-associated-balances"]').length ||
associatedAmount != '0.00'
) {
vegaWalletTeardownVesting(vestingContract);
vegaWalletTeardownStaking(stakingBridgeContract);
vegaWalletTeardownVesting(vestingContract);
}
});
cy.get(vegaWalletContainer).within(() => {
cy.getByTestId('associated-amount', {
cy.get(associatedAmountInWallet, {
timeout: transactionTimeout,
}).contains('0.00', {
timeout: transactionTimeout,
@@ -90,7 +91,7 @@ export async function vegaWalletSetSpecifiedApprovalAmount(
await promiseWithTimeout(
token.approve(
ethStakingBridgeContractAddress,
resetAmount.concat('000000000000000000')
resetAmount + '0'.repeat(18)
),
10 * 60 * 1000,
'set approval amount'
@@ -104,12 +105,23 @@ async function vegaWalletTeardownStaking(stakingBridgeContract: StakingBridge) {
{ timeout: transactionTimeout, log: false }
).then((stakeBalance) => {
if (Number(stakeBalance) != 0) {
cy.wrap(
stakingBridgeContract.remove_stake(
String(stakeBalance),
vegaWalletPubKey
),
{ timeout: transactionTimeout, log: false }
cy.get('[data-testid="vega-wallet-balance-unstaked"]:visible').within(
() => {
cy.get(associatedAmountInWallet)
.invoke('text')
.then(($walletAmount) => {
cy.wrap(
stakingBridgeContract.remove_stake(
String(stakeBalance),
vegaWalletPubKey
),
{ timeout: transactionTimeout, log: false }
);
cy.get(associatedAmountInWallet, {
timeout: transactionTimeout,
}).should('not.have.text', $walletAmount);
});
}
);
}
});
@@ -124,7 +136,6 @@ async function vegaWalletTeardownVesting(vestingContract: TokenVesting) {
if (Number(vestingAmount) != 0) {
// Wait needed to allow time for ganache to process tx for stakingBridgeContract.remove_stake
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
cy.wrap(
vestingContract.remove_stake(String(vestingAmount), vegaWalletPubKey),
{ timeout: transactionTimeout, log: false }
-12
View File
@@ -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
-9
View File
@@ -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
+1
View File
@@ -8,3 +8,4 @@ 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_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
+4 -8
View File
@@ -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',
@@ -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),
@@ -443,8 +443,8 @@
"rewardType": "Reward type",
"rewardsAndFeesReceived": "Rewards and fees received",
"ThisDoesNotIncludeFeesReceivedForMakersOrLiquidityProviders": "This does not include fees received for makers or liquidity providers",
"totalDistributed": "TOTAL DISTRIBUTED",
"earnedByMe": "EARNED BY ME",
"totalDistributed": "Total distributed",
"earnedByMe": "Earned by me",
"noRewardsHaveBeenDistributedYet": "NO REWARDS HAVE BEEN DISTRIBUTED YET",
"rewardsColAssetHeader": "ASSET",
"rewardsColStakingHeader": "STAKING",
@@ -114,7 +114,7 @@ export const RewardsPage = () => {
</p>
</div>
<div className="w-[440px]">
<div className="w-[360px]">
<Toggle
name="epoch-reward-view-toggle"
toggles={[
@@ -7,9 +7,10 @@ query PreviousEpoch($epochId: ID) {
id
rewardScore {
rawValidatorScore
performanceScore
}
rankingScore {
performanceScore
stakeScore
}
}
}
@@ -8,7 +8,7 @@ export type PreviousEpochQueryVariables = Types.Exact<{
}>;
export type PreviousEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, validatorsConnection?: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string } | null, rankingScore: { __typename?: 'RankingScore', performanceScore: string } } } | null> | null } | null } };
export type PreviousEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, validatorsConnection?: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string, performanceScore: string } | null, rankingScore: { __typename?: 'RankingScore', stakeScore: string } } } | null> | null } | null } };
export const PreviousEpochDocument = gql`
@@ -21,9 +21,10 @@ export const PreviousEpochDocument = gql`
id
rewardScore {
rawValidatorScore
performanceScore
}
rankingScore {
performanceScore
stakeScore
}
}
}
@@ -81,9 +81,10 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
id: 'ccc022b7e63a4d0a6d3a193c3940c88574060e58a184964c994998d86835a1b4',
rewardScore: {
rawValidatorScore: '0.25',
performanceScore: '0.9998677767864936',
},
rankingScore: {
performanceScore: '0.9998677767864936',
stakeScore: '0.2499583402766206',
},
},
},
@@ -92,9 +93,10 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
id: '966438c6bffac737cfb08173ffcb3f393c4692b099ad80cb45a82e2dc0a8cf99',
rewardScore: {
rawValidatorScore: '0.3',
performanceScore: '1',
},
rankingScore: {
performanceScore: '1',
stakeScore: '0.25',
},
},
},
@@ -103,9 +105,10 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
id: '12c81b738e8051152e1afe44376ec37bca9216466e6d44cdd772194bad0ada81',
rewardScore: {
rawValidatorScore: '0.35',
performanceScore: '0.999629748500531',
},
rankingScore: {
performanceScore: '0.999629748500531',
stakeScore: '0.2312',
},
},
},
@@ -10,7 +10,6 @@ import {
getFormattedPerformanceScore,
getLastEpochScoreAndPerformance,
getNormalisedVotingPower,
getOverstakedAmount,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
@@ -52,7 +51,6 @@ interface CanonisedConsensusNodeProps {
[ValidatorFields.STAKED_BY_OPERATOR]: string;
[ValidatorFields.PERFORMANCE_SCORE]: string;
[ValidatorFields.PERFORMANCE_PENALTY]: string;
[ValidatorFields.OVERSTAKED_AMOUNT]: string;
[ValidatorFields.OVERSTAKING_PENALTY]: string;
[ValidatorFields.TOTAL_PENALTIES]: string;
[ValidatorFields.PENDING_STAKE]: string;
@@ -164,14 +162,11 @@ export const ConsensusValidatorsTable = ({
pendingUserStake,
userStakeShare,
}) => {
const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
const overstakedAmount = getOverstakedAmount(
rawValidatorScore,
stakedTotal,
totalStake
);
const {
rawValidatorScore: previousEpochValidatorScore,
performanceScore: previousEpochPerformanceScore,
stakeScore: previousEpochStakeScore,
} = getLastEpochScoreAndPerformance(previousEpochData, id);
return {
id,
@@ -184,7 +179,7 @@ export const ConsensusValidatorsTable = ({
[ValidatorFields.NORMALISED_VOTING_POWER]:
getNormalisedVotingPower(votingPower),
[ValidatorFields.UNNORMALISED_VOTING_POWER]:
getUnnormalisedVotingPower(rawValidatorScore),
getUnnormalisedVotingPower(previousEpochValidatorScore),
[ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore),
[ValidatorFields.STAKED_BY_DELEGATES]: formatNumber(
toBigNum(stakedByDelegates, decimals),
@@ -194,18 +189,19 @@ export const ConsensusValidatorsTable = ({
toBigNum(stakedByOperator, decimals),
2
),
[ValidatorFields.PERFORMANCE_SCORE]:
getFormattedPerformanceScore(performanceScore).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]:
getPerformancePenalty(performanceScore),
[ValidatorFields.OVERSTAKED_AMOUNT]: overstakedAmount.toString(),
[ValidatorFields.PERFORMANCE_SCORE]: getFormattedPerformanceScore(
previousEpochPerformanceScore
).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]: getPerformancePenalty(
previousEpochPerformanceScore
),
[ValidatorFields.OVERSTAKING_PENALTY]: getOverstakingPenalty(
overstakedAmount,
totalStake
previousEpochValidatorScore,
previousEpochStakeScore
),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
rawValidatorScore,
performanceScore,
previousEpochValidatorScore,
previousEpochPerformanceScore,
stakedTotal,
totalStake
),
@@ -7,7 +7,6 @@ import { BigNumber } from '../../../../lib/bignumber';
import {
getFormattedPerformanceScore,
getLastEpochScoreAndPerformance,
getOverstakedAmount,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
@@ -82,21 +81,21 @@ export const StandbyPendingValidatorsTable = ({
pendingUserStake,
userStakeShare,
}) => {
const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
const {
rawValidatorScore: previousEpochValidatorScore,
performanceScore: previousEpochPerformanceScore,
stakeScore: previousEpochStakeScore,
} = getLastEpochScoreAndPerformance(previousEpochData, id);
const overstakedAmount = getOverstakedAmount(
rawValidatorScore,
stakedTotal,
totalStake
);
let individualStakeNeededForPromotion,
individualStakeNeededForPromotionDescription;
if (stakeNeededForPromotion && performanceScore) {
if (stakeNeededForPromotion && previousEpochPerformanceScore) {
const stakedTotalBigNum = new BigNumber(stakedTotal);
const stakeNeededBigNum = new BigNumber(stakeNeededForPromotion);
const performanceScoreBigNum = new BigNumber(performanceScore);
const performanceScoreBigNum = new BigNumber(
previousEpochPerformanceScore
);
const calc = stakeNeededBigNum
.dividedBy(performanceScoreBigNum)
@@ -142,18 +141,19 @@ export const StandbyPendingValidatorsTable = ({
toBigNum(stakedByOperator, decimals),
2
),
[ValidatorFields.PERFORMANCE_SCORE]:
getFormattedPerformanceScore(performanceScore).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]:
getPerformancePenalty(performanceScore),
[ValidatorFields.OVERSTAKED_AMOUNT]: overstakedAmount.toString(),
[ValidatorFields.PERFORMANCE_SCORE]: getFormattedPerformanceScore(
previousEpochPerformanceScore
).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]: getPerformancePenalty(
previousEpochPerformanceScore
),
[ValidatorFields.OVERSTAKING_PENALTY]: getOverstakingPenalty(
overstakedAmount,
totalStake
previousEpochValidatorScore,
previousEpochStakeScore
),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
rawValidatorScore,
performanceScore,
previousEpochValidatorScore,
previousEpochPerformanceScore,
stakedTotal,
totalStake
),
@@ -155,16 +155,16 @@ export const ValidatorTables = ({
return (
<section data-testid="validator-tables">
<div className="grid w-full justify-end">
<div className="w-[400px]">
<div className="w-[340px]">
<Toggle
name="validators-view-toggle"
toggles={[
{
label: t('ALL VALIDATORS'),
label: t('All validators'),
value: 'all',
},
{
label: t('STAKED BY ME'),
label: t('Staked by me'),
value: 'myStake',
},
]}
@@ -20,7 +20,6 @@ import { SubHeading } from '../../../components/heading';
import {
getLastEpochScoreAndPerformance,
getNormalisedVotingPower,
getOverstakedAmount,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
@@ -75,15 +74,9 @@ export const ValidatorTable = ({
const stakedOnNode = toBigNum(node.stakedTotal, decimals);
const { rawValidatorScore, performanceScore } =
const { rawValidatorScore, performanceScore, stakeScore } =
getLastEpochScoreAndPerformance(previousEpochData, node.id);
const overstakedAmount = getOverstakedAmount(
rawValidatorScore,
stakedTotal,
node.stakedTotal
);
const stakePercentage = getStakePercentage(total, stakedOnNode);
const totalPenaltiesAmount = getTotalPenalties(
@@ -245,7 +238,7 @@ export const ValidatorTable = ({
<Tooltip description={t('OverstakedPenaltyDescription')}>
<span data-testid="overstaking-penalty">
{getOverstakingPenalty(overstakedAmount, node.stakedTotal)}
{getOverstakingPenalty(rawValidatorScore, stakeScore)}
</span>
</Tooltip>
</KeyValueTableRow>
@@ -4,7 +4,6 @@ import {
getNormalisedVotingPower,
getUnnormalisedVotingPower,
getOverstakingPenalty,
getOverstakedAmount,
getFormattedPerformanceScore,
getPerformancePenalty,
getTotalPenalties,
@@ -22,9 +21,10 @@ describe('getLastEpochScoreAndPerformance', () => {
id: '0x123',
rewardScore: {
rawValidatorScore: '0.25',
performanceScore: '0.75',
},
rankingScore: {
performanceScore: '0.75',
stakeScore: '0.25',
},
},
},
@@ -33,9 +33,10 @@ describe('getLastEpochScoreAndPerformance', () => {
id: '0x234',
rewardScore: {
rawValidatorScore: '0.35',
performanceScore: '0.85',
},
rankingScore: {
performanceScore: '0.85',
stakeScore: '0.25',
},
},
},
@@ -50,12 +51,14 @@ describe('getLastEpochScoreAndPerformance', () => {
).toEqual({
rawValidatorScore: '0.25',
performanceScore: '0.75',
stakeScore: '0.25',
});
expect(
getLastEpochScoreAndPerformance(mockPreviousEpochData, '0x234')
).toEqual({
rawValidatorScore: '0.35',
performanceScore: '0.85',
stakeScore: '0.25',
});
});
});
@@ -79,40 +82,34 @@ describe('getUnnormalisedVotingPower', () => {
});
describe('getOverstakingPenalty', () => {
it('should return the overstaking penalty', () => {
expect(
getOverstakingPenalty(new BigNumber(100), Number(1000).toString())
).toEqual('10.00%');
expect(
getOverstakingPenalty(new BigNumber(500), Number(2000).toString())
).toEqual('25.00%');
});
});
describe('getOverstakedAmount', () => {
it('should return the overstaked amount', () => {
expect(
// If a validator score is 0, any amount staked on the node is considered overstaked
getOverstakedAmount('0', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(20));
expect(
getOverstakedAmount('0.05', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(15));
expect(
getOverstakedAmount('0.1', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(10));
expect(
getOverstakedAmount('0.15', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(5));
expect(
getOverstakedAmount('0.2', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(0));
it('returns "0%" when both arguments are null or undefined', () => {
expect(getOverstakingPenalty(null, null)).toBe('0%');
expect(getOverstakingPenalty(undefined, undefined)).toBe('0%');
expect(getOverstakingPenalty(null, undefined)).toBe('0%');
expect(getOverstakingPenalty(undefined, null)).toBe('0%');
});
it('should return 0 if the overstaked amount is negative', () => {
expect(
getOverstakedAmount('0.8', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(0));
it('returns "0%" when one argument is null or undefined', () => {
expect(getOverstakingPenalty('10', null)).toBe('0%');
expect(getOverstakingPenalty(null, '20')).toBe('0%');
expect(getOverstakingPenalty('10', undefined)).toBe('0%');
expect(getOverstakingPenalty(undefined, '20')).toBe('0%');
});
it('returns "0%" when validatorScore or stakeScore is zero', () => {
expect(getOverstakingPenalty('0', '20')).toBe('0%');
expect(getOverstakingPenalty('10', '0')).toBe('0%');
});
it('returns the correct overstaking penalty', () => {
expect(getOverstakingPenalty('0.18', '0.2')).toBe('10.00%');
expect(getOverstakingPenalty('0.2', '0.2')).toBe('0.00%');
expect(getOverstakingPenalty('0.04', '0.2')).toBe('80.00%');
});
it('handles string numbers with decimals', () => {
expect(getOverstakingPenalty('7.5', '15')).toBe('50.00%');
expect(getOverstakingPenalty('12.5', '25')).toBe('50.00%');
});
});
+16 -20
View File
@@ -15,7 +15,8 @@ export const getLastEpochScoreAndPerformance = (
return {
rawValidatorScore: validator?.rewardScore?.rawValidatorScore,
performanceScore: validator?.rankingScore?.performanceScore,
performanceScore: validator?.rewardScore?.performanceScore,
stakeScore: validator?.rankingScore?.stakeScore,
};
};
@@ -42,31 +43,26 @@ export const getPerformancePenalty = (performanceScore?: string) =>
2
);
export const getOverstakedAmount = (
validatorScore: string | null | undefined,
totalStake: string,
stakedOnNode: string
) => {
const toReturn = validatorScore
? new BigNumber(stakedOnNode).minus(
new BigNumber(validatorScore).times(new BigNumber(totalStake))
)
: new BigNumber(0);
return toReturn.isNegative() ? new BigNumber(0) : toReturn;
};
export const getOverstakingPenalty = (
overstakedAmount: BigNumber,
stakedOnNode: string
validatorScore: string | null | undefined,
stakeScore: string | null | undefined
) => {
if (!validatorScore || !stakeScore) {
return '0%';
}
// avoid division by zero
if (new BigNumber(stakedOnNode).isZero() || overstakedAmount.isZero()) {
return '0';
if (
new BigNumber(validatorScore).isZero() ||
new BigNumber(stakeScore).isZero()
) {
return '0%';
}
return formatNumberPercentage(
overstakedAmount.dividedBy(new BigNumber(stakedOnNode)).times(100),
new BigNumber(1)
.minus(new BigNumber(validatorScore).dividedBy(new BigNumber(stakeScore)))
.times(100),
2
);
};
@@ -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 (
<div className="token-details">
<RoundedWrapper>
@@ -65,18 +69,20 @@ export const TokenDetails = ({
{token.address}
</Link>
</KeyValueTableRow>
<KeyValueTableRow>
{t('Vesting contract').toUpperCase()}
<Link
data-testid="token-contract"
title={t('View on Etherscan (opens in a new tab)')}
className="font-mono text-white text-right"
href={`${ETHERSCAN_URL}/address/${config.token_vesting_contract.address}`}
target="_blank"
>
{config.token_vesting_contract.address}
</Link>
</KeyValueTableRow>
{tokenVestingContractAddress && (
<KeyValueTableRow>
{t('Vesting contract').toUpperCase()}
<Link
data-testid="token-contract"
title={t('View on Etherscan (opens in a new tab)')}
className="font-mono text-white text-right"
href={`${ETHERSCAN_URL}/address/${tokenVestingContractAddress}`}
target="_blank"
>
{tokenVestingContractAddress}
</Link>
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('Total supply').toUpperCase()}
<span className="font-mono" data-testid="total-supply">
-5
View File
@@ -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
File diff suppressed because it is too large Load Diff
@@ -1,3 +0,0 @@
{
"hosts": ["https://api-n00.mainnet-mirror.vega.rocks/graphql"]
}
@@ -1,3 +0,0 @@
{
"hosts": ["https://api-n01.sandbox.vega.rocks/graphql"]
}
@@ -411,6 +411,7 @@ describe('capsule', { tags: '@slow' }, () => {
it('approved amount is less than deposit', function () {
// 1001-DEPO-006
// 1001-DEPO-007
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
@@ -430,8 +431,8 @@ describe('capsule', { tags: '@slow' }, () => {
});
it('withdraw - delay verification', function () {
// 1001-DEPO-007
// 1001-DEPO-024
// 1002-WITH-007
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
@@ -76,6 +76,7 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
});
it('insufficient funds', () => {
// 1001-DEPO-004
mockWeb3DepositCalls({
allowance: '1000',
depositLifetimeLimit: '1000',
@@ -87,7 +88,10 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
.clear()
.type('850')
.next(`[data-testid="${formFieldError}"]`)
.should('have.text', 'Insufficient amount in Ethereum wallet');
.should(
'have.text',
"You can't deposit more than you have in your Ethereum wallet, 800 tEURO"
);
});
});
@@ -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' }, () => {
+81 -1
View File
@@ -1,5 +1,5 @@
import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { aliasGQLQuery, checkSorting } from '@vegaprotocol/cypress';
import { marketsQuery } from '@vegaprotocol/mock';
import { getDateTimeFormat } from '@vegaprotocol/utils';
@@ -85,6 +85,7 @@ describe('markets table', { tags: '@smoke' }, () => {
cy.getByTestId('view-market-list-link')
.should('have.attr', 'href', '#/markets/all')
.click();
cy.get('[data-testid="All markets"]').should(
'have.attr',
'data-state',
@@ -117,6 +118,85 @@ describe('markets table', { tags: '@smoke' }, () => {
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
);
});
it('proposed markets tab should be sorted properly', () => {
cy.getByTestId('view-market-list-link').click();
cy.get('[data-testid="Proposed markets"]').click();
const marketColDefault = [
'ETHUSD',
'LINKUSD',
'ETHUSD',
'ETHDAI.MF21',
'AAPL.MF21',
'BTCUSD.MF21',
'TSLA.QM21',
'AAVEDAI.MF21',
'ETHBTC.QM21',
'UNIDAI.MF21',
];
const marketColAsc = [
'AAPL.MF21',
'AAVEDAI.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'ETHDAI.MF21',
'ETHUSD',
'ETHUSD',
'LINKUSD',
'TSLA.QM21',
'UNIDAI.MF21',
];
const marketColDesc = [
'UNIDAI.MF21',
'TSLA.QM21',
'LINKUSD',
'ETHUSD',
'ETHUSD',
'ETHDAI.MF21',
'ETHBTC.QM21',
'BTCUSD.MF21',
'AAVEDAI.MF21',
'AAPL.MF21',
];
checkSorting('market', marketColDefault, marketColAsc, marketColDesc);
const stateColDefault = [
'Open',
'Passed',
'Waiting for Node Vote',
'Open',
'Passed',
'Open',
'Passed',
'Open',
'Waiting for Node Vote',
'Open',
];
const stateColAsc = [
'Open',
'Open',
'Open',
'Open',
'Open',
'Passed',
'Passed',
'Passed',
'Waiting for Node Vote',
'Waiting for Node Vote',
];
const stateColDesc = [
'Waiting for Node Vote',
'Waiting for Node Vote',
'Passed',
'Passed',
'Passed',
'Open',
'Open',
'Open',
'Open',
'Open',
];
checkSorting('state', stateColDefault, stateColAsc, stateColDesc);
});
it('opening auction subsets should be properly displayed', () => {
cy.mockTradingPage(
@@ -41,6 +41,12 @@ describe('accounts', { tags: '@smoke' }, () => {
.should('have.text', '100,001.01');
});
it('asset detail should be properly rendered', () => {
cy.getByTestId('Collateral').click();
cy.getByTestId('asset').contains('tEURO').click();
cy.get('[data-testid$="_label"]').should('have.length', 16);
});
describe('sorting by ag-grid columns should work well', () => {
it('sorting by asset', () => {
cy.getByTestId('Collateral').click();
@@ -1,5 +1,5 @@
import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery, mockConnectWallet } from '@vegaprotocol/cypress';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { testOrderSubmission } from '../support/order-validation';
import type { OrderSubmission } from '@vegaprotocol/wallet';
import {
@@ -13,8 +13,6 @@ const orderSizeField = 'order-size';
const orderPriceField = 'order-price';
const orderTIFDropDown = 'order-tif';
const placeOrderBtn = 'place-order';
const toggleShort = 'order-side-SIDE_SELL';
const toggleLong = 'order-side-SIDE_BUY';
const toggleLimit = 'order-type-TYPE_LIMIT';
const toggleMarket = 'order-type-TYPE_MARKET';
@@ -32,34 +30,6 @@ const displayTomorrow = () => {
return tomorrow.toISOString().substring(0, 16);
};
describe('time in force default values', () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must have market order set up to IOC by default', function () {
// 7002-SORD-031
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'IOC')[0].text
);
});
it('must have time in force set to GTC for limit order', function () {
// 7002-SORD-031
cy.getByTestId(toggleLimit).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'GTC')[0].text
);
});
});
describe('must submit order', { tags: '@smoke' }, () => {
// 7002-SORD-039
before(() => {
@@ -393,227 +363,6 @@ describe(
}
);
describe('deal ticket validation', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must show place order button and connect wallet if wallet is not connected', () => {
// 0003-WTXN-001
cy.getByTestId('connect-vega-wallet'); // Not connected
cy.getByTestId('order-connect-wallet').should('exist');
cy.getByTestId(placeOrderBtn).should('exist');
cy.getByTestId('deal-ticket-connect-wallet').should('exist');
});
it('must be able to select order direction - long/short', function () {
// 7002-SORD-004
cy.getByTestId(toggleShort).click().children('input').should('be.checked');
cy.getByTestId(toggleLong).click().children('input').should('be.checked');
});
it('must be able to select order type - limit/market', function () {
// 7002-SORD-005
// 7002-SORD-006
// 7002-SORD-007
cy.getByTestId(toggleLimit).click().children('input').should('be.checked');
cy.getByTestId(toggleMarket).click().children('input').should('be.checked');
});
it('order connect vega wallet button should connect', () => {
mockConnectWallet();
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('101');
cy.getByTestId('order-connect-wallet').click();
cy.getByTestId('dialog-content').should('be.visible');
cy.getByTestId('connectors-list')
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.wait('@walletReq');
cy.getByTestId(placeOrderBtn).should('be.visible');
cy.getByTestId(toggleLimit).children('input').should('be.checked');
cy.getByTestId(orderPriceField).should('have.value', '101');
});
});
describe('deal ticket size validation', { tags: '@smoke' }, function () {
beforeEach(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must warn if order size input has too many digits after the decimal place', function () {
// 7002-SORD-016
cy.getByTestId('order-type-TYPE_MARKET').click();
cy.getByTestId(orderSizeField).clear().type('1.234');
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId('dealticket-error-message-size-market').should(
'have.text',
'Size must be whole numbers for this market'
);
});
it('must warn if order size is set to 0', function () {
cy.getByTestId('order-type-TYPE_MARKET').click();
cy.getByTestId(orderSizeField).clear().type('0');
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId('dealticket-error-message-size-market').should(
'have.text',
'Size cannot be lower than 1'
);
});
});
describe('limit order validations', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(toggleLimit).click();
});
beforeEach(() => {
cy.setVegaWallet();
});
it('must see the price unit', function () {
// 7002-SORD-018
cy.getByTestId(orderPriceField)
.siblings('label')
.should('have.text', 'Price (DAI)');
});
it('must see warning when placing an order with expiry date in past', () => {
const expiresAt = new Date(Date.now() - 24 * 60 * 60 * 1000);
const expiresAtInputValue = expiresAt.toISOString().substring(0, 16);
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('0.1');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
cy.log('choosing yesterday');
cy.getByTestId('date-picker-field').type(expiresAtInputValue);
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('dealticket-error-message-expiry').should(
'have.text',
'The expiry date that you have entered appears to be in the past'
);
});
it('must see warning if price has too many digits after decimal place', function () {
// 7002-SORD-059
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTC');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderPriceField).clear().type('1.123456');
cy.getByTestId('dealticket-error-message-price-limit').should(
'have.text',
'Price accepts up to 5 decimal places'
);
});
describe('time in force validations', function () {
const validTIF = TIFlist;
validTIF.forEach((tif) => {
// 7002-SORD-023
// 7002-SORD-024
// 7002-SORD-025
// 7002-SORD-026
// 7002-SORD-027
// 7002-SORD-028
it(`must be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
});
});
it('selections should be remembered', () => {
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'IOC')[0].text
);
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_FOK');
cy.getByTestId(toggleLimit).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'GTT')[0].text
);
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'FOK')[0].text
);
});
});
});
describe('market order validations', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(toggleMarket).click();
});
beforeEach(() => {
cy.setVegaWallet();
});
it('must not see the price unit', function () {
// 7002-SORD-019
cy.getByTestId(orderPriceField).should('not.exist');
});
describe('time in force validations', function () {
const validTIF = TIFlist.filter((tif) => ['FOK', 'IOC'].includes(tif.code));
const invalidTIF = TIFlist.filter(
(tif) => !['FOK', 'IOC'].includes(tif.code)
);
validTIF.forEach((tif) => {
// 7002-SORD-025
// 7002-SORD-026
it(`must be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
});
});
invalidTIF.forEach((tif) => {
// 7002-SORD-023
// 7002-SORD-024
// 7002-SORD-027
// 7002-SORD-028
it(`must not be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).should('not.contain', tif.text);
});
});
});
});
describe('suspended market validation', { tags: '@regression' }, () => {
before(() => {
cy.setVegaWallet();
@@ -765,6 +514,8 @@ describe('account validation', { tags: '@regression' }, () => {
it('must show error returned by wallet ', () => {
// 0003-WTXN-009
// 0003-WTXN-011
// 0002-WCON-016
// 0003-WTXN-008
//trigger error from the wallet
cy.intercept('POST', 'http://localhost:1789/api/v2/requests', (req) => {
@@ -788,6 +539,8 @@ describe('account validation', { tags: '@regression' }, () => {
'contain.text',
'The connection to your Vega Wallet has been lost.'
);
cy.getByTestId('connect-vega-wallet').click();
cy.getByTestId('dialog-content').should('be.visible');
});
it('must see that the order was rejected by the connected wallet', () => {
@@ -0,0 +1,329 @@
import * as Schema from '@vegaprotocol/types';
import { mockConnectWallet } from '@vegaprotocol/cypress';
const orderSizeField = 'order-size';
const orderPriceField = 'order-price';
const orderTIFDropDown = 'order-tif';
const placeOrderBtn = 'place-order';
const toggleShort = 'order-side-SIDE_SELL';
const toggleLong = 'order-side-SIDE_BUY';
const toggleLimit = 'order-type-TYPE_LIMIT';
const toggleMarket = 'order-type-TYPE_MARKET';
const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
return {
code: Schema.OrderTimeInForceCode[value],
value,
text: Schema.OrderTimeInForceMapping[value],
};
});
describe('time in force default values', () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must have market order set up to IOC by default', function () {
// 7002-SORD-030
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'IOC')[0].text
);
});
it('must have time in force set to GTC for limit order', function () {
// 7002-SORD-031
cy.getByTestId(toggleLimit).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'GTC')[0].text
);
});
});
describe('deal ticket validation', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must show place order button and connect wallet if wallet is not connected', () => {
// 0003-WTXN-001
cy.getByTestId('connect-vega-wallet'); // Not connected
cy.getByTestId('order-connect-wallet').should('exist');
cy.getByTestId(placeOrderBtn).should('exist');
cy.getByTestId('deal-ticket-connect-wallet').should('exist');
});
it('must be able to select order direction - long/short', function () {
// 7002-SORD-004
cy.getByTestId(toggleShort).click().children('input').should('be.checked');
cy.getByTestId(toggleLong).click().children('input').should('be.checked');
});
it('must be able to select order type - limit/market', function () {
// 7002-SORD-005
// 7002-SORD-006
// 7002-SORD-007
cy.getByTestId(toggleLimit).click().children('input').should('be.checked');
cy.getByTestId(toggleMarket).click().children('input').should('be.checked');
});
it('order connect vega wallet button should connect', () => {
mockConnectWallet();
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('101');
cy.getByTestId('order-connect-wallet').click();
cy.getByTestId('dialog-content').should('be.visible');
cy.getByTestId('connectors-list')
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.wait('@walletReq');
cy.getByTestId(placeOrderBtn).should('be.visible');
cy.getByTestId(toggleLimit).children('input').should('be.checked');
cy.getByTestId(orderPriceField).should('have.value', '101');
});
});
describe('deal ticket size validation', { tags: '@smoke' }, function () {
beforeEach(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must warn if order size input has too many digits after the decimal place', function () {
// 7002-SORD-016
cy.getByTestId('order-type-TYPE_MARKET').click();
cy.getByTestId(orderSizeField).clear().type('1.234');
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId('dealticket-error-message-size-market').should(
'have.text',
'Size must be whole numbers for this market'
);
});
it('must warn if order size is set to 0', function () {
cy.getByTestId('order-type-TYPE_MARKET').click();
cy.getByTestId(orderSizeField).clear().type('0');
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId('dealticket-error-message-size-market').should(
'have.text',
'Size cannot be lower than 1'
);
});
});
describe('limit order validations', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(toggleLimit).click();
});
beforeEach(() => {
cy.setVegaWallet();
});
it('must see the price unit', function () {
// 7002-SORD-018
cy.getByTestId(orderPriceField)
.siblings('label')
.should('have.text', 'Price (DAI)');
});
it('must see warning when placing an order with expiry date in past', () => {
const expiresAt = new Date(Date.now() - 24 * 60 * 60 * 1000);
const expiresAtInputValue = expiresAt.toISOString().substring(0, 16);
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('0.1');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
cy.log('choosing yesterday');
cy.getByTestId('date-picker-field').type(expiresAtInputValue);
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('dealticket-error-message-expiry').should(
'have.text',
'The expiry date that you have entered appears to be in the past'
);
});
it('must see warning if price has too many digits after decimal place', function () {
// 7002-SORD-059
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTC');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderPriceField).clear().type('1.123456');
cy.getByTestId('dealticket-error-message-price-limit').should(
'have.text',
'Price accepts up to 5 decimal places'
);
});
describe('time in force validations', function () {
const validTIF = TIFlist;
validTIF.forEach((tif) => {
// 7002-SORD-023
// 7002-SORD-024
// 7002-SORD-025
// 7002-SORD-026
// 7002-SORD-027
// 7002-SORD-028
it(`must be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
});
});
it('selections should be remembered', () => {
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'IOC')[0].text
);
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_FOK');
cy.getByTestId(toggleLimit).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'GTT')[0].text
);
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'FOK')[0].text
);
});
});
});
describe('market order validations', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(toggleMarket).click();
});
beforeEach(() => {
cy.setVegaWallet();
});
it('must not see the price unit', function () {
// 7002-SORD-019
cy.getByTestId(orderPriceField).should('not.exist');
});
describe('time in force validations', function () {
const validTIF = TIFlist.filter((tif) => ['FOK', 'IOC'].includes(tif.code));
const invalidTIF = TIFlist.filter(
(tif) => !['FOK', 'IOC'].includes(tif.code)
);
validTIF.forEach((tif) => {
// 7002-SORD-025
// 7002-SORD-026
it(`must be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
});
});
invalidTIF.forEach((tif) => {
// 7002-SORD-023
// 7002-SORD-024
// 7002-SORD-027
// 7002-SORD-028
it(`must not be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).should('not.contain', tif.text);
});
});
});
});
describe('post and reduce order validations', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(toggleMarket).click();
});
beforeEach(() => {
cy.setVegaWallet();
});
const validTIF = TIFlist.filter((tif) => ['FOK', 'IOC'].includes(tif.code));
validTIF.forEach((tif) => {
// 7002-SORD-025
// 7002-SORD-026
it(`post and reduce order market for ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
cy.getByTestId('post-only').should('be.disabled');
cy.getByTestId('reduce-only').should('be.enabled');
});
});
validTIF.forEach((tif) => {
it(`post and reduce order limit for ${tif.code}`, function () {
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
cy.getByTestId('post-only').should('be.disabled');
cy.getByTestId('reduce-only').should('be.enabled');
});
});
const validTIFLimit = TIFlist.filter((tif) =>
['GFA', 'GFN', 'GTC', 'GTT'].includes(tif.code)
);
validTIFLimit.forEach((tif) => {
it(`post and reduce order for limit ${tif.code}`, function () {
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
cy.getByTestId('post-only').should('be.enabled');
cy.getByTestId('reduce-only').should('be.disabled');
});
});
});
@@ -117,6 +117,7 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
// 0002-WCON-002
// 0002-WCON-005
// 0002-WCON-007
// 0002-WCON-009
mockConnectWallet();
cy.getByTestId(connectVegaBtn).click();
@@ -124,6 +125,10 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.wait('@walletReq');
cy.getByTestId(dialogContent).should(
'contain.text',
'Approve the connection from your Vega wallet app.'
);
cy.getByTestId(dialogContent).should('not.exist');
cy.getByTestId(manageVegaBtn).should('exist');
});
@@ -132,6 +137,7 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
// 0002-WCON-002
// 0002-WCON-005
// 0002-WCON-007
// 0002-WCON-015
mockConnectWalletWithUserError();
cy.getByTestId(connectVegaBtn).click();
+52 -163
View File
@@ -1,5 +1,5 @@
import {
getId,
matchFilter,
liquidityProvisionsDataProvider,
LiquidityTable,
lpAggregatedDataProvider,
@@ -8,6 +8,7 @@ import {
import { tooltipMapping } from '@vegaprotocol/market-info';
import {
addDecimalsFormatNumber,
createDocsLinks,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
@@ -17,28 +18,28 @@ import {
useNetworkParams,
updateGridData,
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import {
AsyncRenderer,
Tab,
Tabs,
Link as UiToolkitLink,
Indicator,
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { memo, useCallback, useEffect, useMemo, useRef, useState } 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';
import isEqual from 'lodash/isEqual';
import { useEnvironment } from '@vegaprotocol/environment';
const enum LiquidityTabs {
Active = 'active',
@@ -67,8 +68,10 @@ const useReloadLiquidityData = (marketId: string | undefined) => {
export const LiquidityContainer = ({
marketId,
filter,
}: {
marketId: string | undefined;
filter?: Filter;
}) => {
const gridRef = useRef<AgGridReact | null>(null);
const { data: market } = useMarket(marketId);
@@ -87,7 +90,7 @@ export const LiquidityContainer = ({
const { data, loading, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
update,
variables: { marketId: marketId || '' },
variables: { marketId: marketId || '', filter },
skip: !marketId,
});
@@ -144,6 +147,7 @@ const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const symbol =
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
const { VEGA_DOCS_URL } = useEnvironment();
const { params } = useNetworkParams([
NetworkParams.market_liquidity_stakeToCcyVolume,
@@ -211,183 +215,68 @@ const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
<HeaderStat heading={t('Market ID')}>
<div className="break-word">{marketId}</div>
</HeaderStat>
<HeaderStat heading={t('Learn more')}>
{VEGA_DOCS_URL && (
<ExternalLink href={createDocsLinks(VEGA_DOCS_URL).LIQUIDITY}>
{t('Providing liquidity')}
</ExternalLink>
)}
</HeaderStat>
</Header>
);
});
LiquidityViewHeader.displayName = 'LiquidityViewHeader';
const filterLiquidities = (
tab: string,
liquidities?: LiquidityProvisionData[] | null,
pubKey?: string | null
) => {
switch (tab) {
case LiquidityTabs.MyLiquidityProvision:
return pubKey
? (liquidities || []).filter((e) => e.party.id === pubKey)
: [];
break;
case LiquidityTabs.Active:
return (liquidities || []).filter(
(e) => e.status === Schema.LiquidityProvisionStatus.STATUS_ACTIVE
);
case LiquidityTabs.Inactive:
return (liquidities || []).filter(
(e) => e.status !== Schema.LiquidityProvisionStatus.STATUS_ACTIVE
);
default:
return [];
}
};
export const LiquidityViewContainer = ({
marketId,
}: {
marketId: string | undefined;
}) => {
const [tab, setTab] = useState('');
const [tab, setTab] = useState<string | undefined>(undefined);
const { pubKey } = useVegaWallet();
const [liquidityProviders, setLiquidityProviders] = useState<
LiquidityProvisionData[] | null
>();
const gridRef = useRef<AgGridReact | null>(null);
const { data: market } = useMarket(marketId);
const dataRef = useRef<LiquidityProvisionData[] | null>(null);
// To be removed when liquidityProvision subscriptions are working
useReloadLiquidityData(marketId);
const update = useCallback(
({ data }: { data: LiquidityProvisionData[] | null }) => {
if (!dataRef.current) {
setLiquidityProviders(data);
dataRef.current = data;
}
if (!gridRef.current?.api) {
return false;
}
const updateRows: LiquidityProvisionData[] = [];
const addRows: LiquidityProvisionData[] = [];
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
dataRef.current = data;
gridRef.current.api.refreshInfiniteCache();
} else {
const filteredData = filterLiquidities(tab, data, pubKey as string);
filteredData?.forEach((d) => {
const rowNode = gridRef.current?.api?.getRowNode(getId(d));
if (rowNode) {
if (!isEqual(rowNode.data, d)) {
updateRows.push(d);
}
} else {
addRows.push(d);
}
});
gridRef.current?.api?.applyTransaction({
update: updateRows,
add: addRows,
addIndex: 0,
});
}
return true;
},
[gridRef, tab, pubKey]
);
const { loading, error } = useDataProvider({
const { data } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
update,
skipUpdates: true,
variables: { marketId: marketId || '' },
skip: !marketId,
});
const assetDecimalPlaces =
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const symbol =
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
const { params } = useNetworkParams([
NetworkParams.market_liquidity_stakeToCcyVolume,
NetworkParams.market_liquidity_targetstake_triggering_ratio,
]);
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
const myLpEdges = useMemo(
() =>
filterLiquidities(
LiquidityTabs.MyLiquidityProvision,
liquidityProviders,
pubKey
),
[liquidityProviders, pubKey]
);
const activeEdges = useMemo(
() => filterLiquidities(LiquidityTabs.Active, liquidityProviders),
[liquidityProviders]
);
const inactiveEdges = useMemo(
() => filterLiquidities(LiquidityTabs.Inactive, liquidityProviders),
[liquidityProviders]
);
useEffect(() => {
if (tab) {
return;
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);
}
let initialTab = LiquidityTabs.Active;
if (myLpEdges.length > 0) {
initialTab = LiquidityTabs.MyLiquidityProvision;
}
if (activeEdges?.length) {
initialTab = LiquidityTabs.Active;
} else if (inactiveEdges.length > 0) {
initialTab = LiquidityTabs.Inactive;
}
setTab(initialTab);
}, [tab, myLpEdges?.length, activeEdges?.length, inactiveEdges?.length]);
}, [data, pubKey]);
return (
<AsyncRenderer loading={loading} error={error} data={liquidityProviders}>
<div className="h-full grid grid-rows-[min-content_1fr]">
<LiquidityViewHeader marketId={marketId} />
<Tabs value={tab} onValueChange={setTab}>
<Tab
id={LiquidityTabs.MyLiquidityProvision}
name={t('My liquidity provision')}
hidden={!pubKey}
>
{myLpEdges && (
<LiquidityTable
ref={gridRef}
rowData={myLpEdges}
symbol={symbol}
stakeToCcyVolume={stakeToCcyVolume}
assetDecimalPlaces={assetDecimalPlaces}
/>
)}
</Tab>
<Tab id={LiquidityTabs.Active} name={t('Active')}>
{activeEdges && (
<LiquidityTable
ref={gridRef}
rowData={activeEdges}
symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces}
stakeToCcyVolume={stakeToCcyVolume}
/>
)}
</Tab>
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
{inactiveEdges && (
<LiquidityTable
ref={gridRef}
rowData={inactiveEdges}
symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces}
stakeToCcyVolume={stakeToCcyVolume}
/>
)}
</Tab>
</Tabs>
</div>
</AsyncRenderer>
<div className="h-full grid grid-rows-[min-content_1fr]">
<LiquidityViewHeader marketId={marketId} />
<Tabs value={tab || LiquidityTabs.Active} onValueChange={setTab}>
<Tab
id={LiquidityTabs.MyLiquidityProvision}
name={t('My liquidity provision')}
hidden={!pubKey}
>
<LiquidityContainer
marketId={marketId}
filter={{ partyId: pubKey || undefined }}
/>
</Tab>
<Tab id={LiquidityTabs.Active} name={t('Active')}>
<LiquidityContainer marketId={marketId} filter={{ active: true }} />
</Tab>
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
<LiquidityContainer marketId={marketId} filter={{ active: false }} />
</Tab>
</Tabs>
</div>
);
};
@@ -301,7 +301,7 @@ export const AccountHistoryChart = ({
asset: AssetFieldsFragment;
}) => {
const { theme } = useThemeSwitcher();
const values: { cols: string[]; rows: [Date, ...number[]][] } | null =
const values: { cols: [string, string]; rows: [Date, number][] } | null =
useMemo(() => {
if (!data?.balanceChanges.edges.length) {
return null;
@@ -8,6 +8,7 @@ import type { MarketData } from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
import {
ExternalLink,
Indicator,
KeyValueTable,
KeyValueTableRow,
@@ -18,9 +19,11 @@ import { useCheckLiquidityStatus } from '@vegaprotocol/liquidity';
import { AuctionTrigger, MarketTradingMode } from '@vegaprotocol/types';
import {
addDecimalsFormatNumber,
createDocsLinks,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useEnvironment } from '@vegaprotocol/environment';
interface Props {
marketId?: string;
@@ -44,6 +47,8 @@ export const MarketLiquiditySupplied = ({
params.market_liquidity_targetstake_triggering_ratio
);
const { VEGA_DOCS_URL } = useEnvironment();
const variables = useMemo(
() => ({
marketId: marketId || '',
@@ -126,6 +131,14 @@ export const MarketLiquiditySupplied = ({
<Link href={`/#/liquidity/${marketId}`} data-testid="view-liquidity-link">
{t('View liquidity provision table')}
</Link>
{VEGA_DOCS_URL && (
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).LIQUIDITY}
className="mt-2"
>
{t('Learn about providing liquidity')}
</ExternalLink>
)}
{showMessage && (
<p className="mt-4">
{t(
+5 -1
View File
@@ -23,6 +23,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { Links, Routes } from '../../pages/client-router';
import { createDocsLinks } from '@vegaprotocol/utils';
export const Navbar = ({
theme = 'system',
@@ -37,6 +38,7 @@ export const Navbar = ({
const tradingPath = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.MARKET]();
return (
<Navigation
appName="Console"
@@ -89,7 +91,9 @@ export const Navbar = ({
<NavigationContent>
<NavigationList>
<NavigationItem>
<NavExternalLink href={VEGA_DOCS_URL}>
<NavExternalLink
href={createDocsLinks(VEGA_DOCS_URL).NEW_TO_VEGA}
>
{t('Docs')}
</NavExternalLink>
</NavigationItem>
@@ -185,7 +185,7 @@ export const columns = (
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onSelect(market.id, e.metaKey);
onSelect(market.id, e.metaKey || e.ctrlKey);
}}
>
<UILink>{market.tradableInstrument.instrument.code}</UILink>
@@ -366,7 +366,7 @@ export const columnsPositionMarkets = (
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onSelect(market.id, e.metaKey);
onSelect(market.id, e.metaKey || e.ctrlKey);
}}
>
<UILink>{market.tradableInstrument.instrument.code}</UILink>
@@ -44,7 +44,7 @@ export const SelectMarketTableRow = ({
<tr
className={`hover:bg-neutral-200 dark:hover:bg-neutral-700 cursor-pointer relative h-[34px]`}
onClick={(ev) => {
onSelect(marketId, ev.metaKey);
onSelect(marketId, ev.metaKey || ev.ctrlKey);
}}
data-testid={`market-link-${marketId}`}
>
@@ -8,6 +8,10 @@ import type {
MarketData,
} from '@vegaprotocol/market-list';
import { SelectMarketLandingTable } from './welcome-landing-dialog';
const mockMarketClickHandler = jest.fn();
jest.mock('../../lib/hooks/use-market-click-handler', () => ({
useMarketClickHandler: () => mockMarketClickHandler,
}));
type Market = MarketMaybeWithCandles & MarketMaybeWithData;
type PartialMarket = Partial<
@@ -174,4 +178,25 @@ describe('WelcomeLandingDialog', () => {
fireEvent.click(screen.getAllByTestId(`market-link-2`)[0]);
expect(onClose).toHaveBeenCalled();
});
it('should not call onClose when metaKey is held', () => {
const onClose = jest.fn();
render(
<MemoryRouter>
<SelectMarketLandingTable
markets={[MARKET_A as Market, MARKET_B as Market]}
onClose={onClose}
/>
</MemoryRouter>,
{ wrapper: MockedProvider }
);
fireEvent.click(screen.getAllByTestId(`market-link-1`)[0], {
metaKey: true,
});
expect(mockMarketClickHandler).toHaveBeenCalled();
expect(onClose).not.toHaveBeenCalled();
fireEvent.click(screen.getAllByTestId(`market-link-1`)[0]);
expect(onClose).toHaveBeenCalled();
});
});
@@ -12,9 +12,10 @@ import {
SelectMarketTableRow,
} from '../select-market';
import { WelcomeDialogHeader } from './welcome-dialog-header';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { ProposedMarkets } from './proposed-markets';
import { Links, Routes } from '../../pages/client-router';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const SelectMarketLandingTable = ({
markets,
@@ -23,23 +24,13 @@ export const SelectMarketLandingTable = ({
markets: MarketMaybeWithDataAndCandles[] | null;
onClose: () => void;
}) => {
const params = useParams();
const navigate = useNavigate();
const marketId = params.marketId;
const onSelect = useCallback(
(id: string) => {
if (id && id !== marketId) {
navigate(Links[Routes.MARKET](id));
}
},
[marketId, navigate]
);
const onSelect = useMarketClickHandler();
const onSelectMarket = useCallback(
(id: string) => {
onSelect(id);
onClose();
(id: string, metaKey?: boolean) => {
onSelect(id, metaKey);
if (!metaKey) {
onClose();
}
},
[onSelect, onClose]
);
@@ -73,7 +64,7 @@ export const SelectMarketLandingTable = ({
key={i}
detailed={false}
onSelect={onSelectMarket}
columns={columns(market, onSelect, onCellClick)}
columns={columns(market, onSelectMarket, onCellClick)}
/>
))}
</tbody>
@@ -7,7 +7,11 @@ import {
} from '@vegaprotocol/types';
import type { VegaStoredTxState } from '@vegaprotocol/wallet';
import { VegaTxStatus } from '@vegaprotocol/wallet';
import { VegaTransactionDetails } from './use-vega-transaction-toasts';
import {
VegaTransactionDetails,
getVegaTransactionContentIntent,
} from './use-vega-transaction-toasts';
import { Intent } from '@vegaprotocol/ui-toolkit';
jest.mock('@vegaprotocol/assets', () => {
const A1 = {
@@ -278,3 +282,27 @@ describe('VegaTransactionDetails', () => {
expect(queryByTestId('toast-panel')?.textContent).toEqual(details);
});
});
describe('getVegaTransactionContentIntent', () => {
it('returns the correct intent for a transaction', () => {
expect(getVegaTransactionContentIntent(withdraw).intent).toBe(
Intent.Primary
);
expect(getVegaTransactionContentIntent(submitOrder).intent).toBe(
Intent.Success
);
expect(getVegaTransactionContentIntent(editOrder).intent).toBe(
Intent.Primary
);
expect(getVegaTransactionContentIntent(cancelOrder).intent).toBe(
Intent.Primary
);
expect(getVegaTransactionContentIntent(cancelAll).intent).toBe(
Intent.Primary
);
expect(getVegaTransactionContentIntent(closePosition).intent).toBe(
Intent.Primary
);
expect(getVegaTransactionContentIntent(batch).intent).toBe(Intent.Primary);
});
});
@@ -39,10 +39,14 @@ import { t } from '@vegaprotocol/i18n';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
import { DApp, EXPLORER_TX, useLinks } from '@vegaprotocol/environment';
import { getRejectionReason, useOrderByIdQuery } from '@vegaprotocol/orders';
import {
getOrderToastIntent,
getOrderToastTitle,
getRejectionReason,
useOrderByIdQuery,
} from '@vegaprotocol/orders';
import { useMarketList } from '@vegaprotocol/market-list';
import type { Side } from '@vegaprotocol/types';
import { OrderStatus } from '@vegaprotocol/types';
import { OrderStatusMapping } from '@vegaprotocol/types';
import { Size } from '@vegaprotocol/react-helpers';
@@ -474,10 +478,11 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
}
if (tx.order && tx.order.rejectionReason) {
const rejectionReason = getRejectionReason(tx.order) || ' ';
const rejectionReason =
getRejectionReason(tx.order) || tx.order.rejectionReason || '';
return (
<>
<ToastHeading>{t('Order rejected')}</ToastHeading>
<ToastHeading>{getOrderToastTitle(tx.order.status)}</ToastHeading>
{rejectionReason ? (
<p>
{t('Your order has been rejected because: %s', [rejectionReason])}
@@ -503,7 +508,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
if (isOrderSubmissionTransaction(tx.body) && tx.order?.rejectionReason) {
return (
<div>
<h3 className="font-bold">{t('Order rejected')}</h3>
<h3 className="font-bold">{getOrderToastTitle(tx.order.status)}</h3>
<p>{t('Your order was rejected.')}</p>
{tx.txHash && (
<p className="break-all">
@@ -542,7 +547,11 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
return (
<>
<ToastHeading>{t('Confirmed')}</ToastHeading>
<ToastHeading>
{tx.order?.status
? getOrderToastTitle(tx.order.status)
: t('Confirmed')}
</ToastHeading>
<p>{t('Your transaction has been confirmed ')}</p>
{tx.txHash && (
<p className="break-all">
@@ -577,9 +586,9 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
tx.error instanceof WalletError &&
walletNoConnectionCodes.includes(tx.error.code);
if (orderRejection) {
label = t('Order rejected');
label = getOrderToastTitle(tx.order?.status) || t('Order rejected');
errorMessage = t('Your order has been rejected because: %s', [
orderRejection,
orderRejection || tx.order?.rejectionReason || ' ',
]);
}
if (walletError) {
@@ -629,26 +638,8 @@ export const useVegaTransactionToasts = () => {
);
const fromVegaTransaction = (tx: VegaStoredTxState): Toast => {
let content: ToastContent;
const closeAfter = isFinal(tx) ? CLOSE_AFTER : undefined;
if (tx.status === VegaTxStatus.Requested) {
content = <VegaTxRequestedToastContent tx={tx} />;
}
if (tx.status === VegaTxStatus.Pending) {
content = <VegaTxPendingToastContentProps tx={tx} />;
}
if (tx.status === VegaTxStatus.Complete) {
content = <VegaTxCompleteToastsContent tx={tx} />;
}
if (tx.status === VegaTxStatus.Error) {
content = <VegaTxErrorToastContent tx={tx} />;
}
// Transaction can be successful but the order can be rejected by the network
const intent =
tx.order && [OrderStatus.STATUS_REJECTED].includes(tx.order.status)
? Intent.Danger
: intentMap[tx.status];
const { intent, content } = getVegaTransactionContentIntent(tx);
return {
id: `vega-${tx.id}`,
@@ -672,3 +663,27 @@ export const useVegaTransactionToasts = () => {
}
);
};
export const getVegaTransactionContentIntent = (tx: VegaStoredTxState) => {
let content: ToastContent;
if (tx.status === VegaTxStatus.Requested) {
content = <VegaTxRequestedToastContent tx={tx} />;
}
if (tx.status === VegaTxStatus.Pending) {
content = <VegaTxPendingToastContentProps tx={tx} />;
}
if (tx.status === VegaTxStatus.Complete) {
content = <VegaTxCompleteToastsContent tx={tx} />;
}
if (tx.status === VegaTxStatus.Error) {
content = <VegaTxErrorToastContent tx={tx} />;
}
// Transaction can be successful but the order can be rejected by the network
const intent =
(tx.order &&
!isOrderAmendmentTransaction(tx.body) &&
getOrderToastIntent(tx.order.status)) ||
intentMap[tx.status];
return { intent, content };
};
+15
View File
@@ -34,6 +34,18 @@ html [data-theme='dark'] {
/* sell candles only use stroke as the candle is solid (without border) */
--pennant-color-sell-stroke: theme('colors.vega.pink.500');
/* studies */
--pennant-color-eldar-ray-bear-power: theme('colors.vega.pink.500');
--pennant-color-eldar-ray-bull-power: theme('colors.vega.green.650');
--pennant-color-macd-divergence-buy: theme('colors.vega.green.650');
--pennant-color-macd-divergence-sell: theme('colors.vega.pink.500');
--pennant-color-macd-signal: theme('colors.vega.blue.500');
--pennant-color-macd-macd: theme('colors.vega.yellow.500');
--pennant-color-volume-buy: theme('colors.vega.green.650');
--pennant-color-volume-sell: theme('colors.vega.pink.500');
/* depth chart */
--pennant-color-depth-buy-fill: theme('colors.vega.green.650');
--pennant-color-depth-buy-stroke: theme('colors.vega.green.500');
@@ -50,6 +62,9 @@ html [data-theme='light'] {
/* sell candles only use stroke as the candle is solid (without border) */
--pennant-color-sell-stroke: theme('colors.vega.pink.400');
--pennant-color-volume-buy: theme('colors.vega.green.400');
--pennant-color-volume-sell: theme('colors.vega.pink.400');
/* depth chart */
--pennant-color-depth-buy-fill: theme('colors.vega.green.400');
--pennant-color-depth-buy-stroke: theme('colors.vega.green.550');
-10
View File
@@ -1,10 +0,0 @@
#!/bin/sh -eux
export PATH="/app/node_modules/.bin:$PATH"
if [ "${APP}" = "trading" ]; then
yarn nx export ${APP} --network-timeout 100000 --pure-lockfile
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
fi
+6
View File
@@ -0,0 +1,6 @@
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
EXPOSE 80
WORKDIR /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY ./dist-result/ /usr/share/nginx/html/
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash -ex
export PATH="/app/node_modules/.bin:$PATH"
flags="--network-timeout 100000 --pure-lockfile"
if [[ ! -z "${ENV_NAME}" ]]; then
if [[ "${ENV_NAME}" != "ops-vega" ]]; then
flags="--env=${ENV_NAME} $flags"
fi
fi
if [ "${APP}" = "trading" ]; then
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} $flags
fi
+6 -10
View File
@@ -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 \
@@ -12,22 +13,17 @@ RUN apk add --update --no-cache \
COPY . ./
RUN yarn --network-timeout 100000 --pure-lockfile
# work around for different build process in trading
RUN sh ./docker-build.sh
RUN sh docker/docker-build.sh
# Server environment
# 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
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY --from=build /app/dist/apps/${APP}/* /usr/share/nginx/html
RUN apk add --no-cache go-ipfs; ipfs init && echo "$(ipfs add -rQ .)" > /ipfs-hash; apk del go-ipfs
-43
View File
@@ -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;'
+221 -72
View File
@@ -1,4 +1,10 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import {
act,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import BigNumber from 'bignumber.js';
import { AddressField, TransferFee, TransferForm } from './transfer-form';
import { AccountType } from '@vegaprotocol/types';
@@ -27,6 +33,34 @@ describe('TransferForm', () => {
submitTransfer: jest.fn(),
};
it('validates a manually entered address', async () => {
render(<TransferForm {...props} />);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
const toggle = screen.getByText('Enter manually');
fireEvent.click(toggle);
// has switched to input
expect(toggle).toHaveTextContent('Select from wallet');
expect(screen.getByLabelText('Vega key')).toHaveAttribute('type', 'text');
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: 'invalid-address' },
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Invalid Vega key');
});
// same pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: pubKey },
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Vega key is the same');
});
});
it('validates fields and submits', async () => {
render(<TransferForm {...props} />);
@@ -62,15 +96,17 @@ describe('TransferForm', () => {
formatNumber(asset.balance, asset.decimals)
);
const amountInput = screen.getByLabelText('Amount');
// Test amount validation
fireEvent.change(screen.getByLabelText('Amount'), {
fireEvent.change(amountInput, {
target: { value: '0.00000001' },
});
expect(
await screen.findByText('Value is below minimum')
).toBeInTheDocument();
fireEvent.change(screen.getByLabelText('Amount'), {
fireEvent.change(amountInput, {
target: { value: '9999999' },
});
expect(
@@ -78,7 +114,7 @@ describe('TransferForm', () => {
).toBeInTheDocument();
// set valid amount
fireEvent.change(screen.getByLabelText('Amount'), {
fireEvent.change(amountInput, {
target: { value: amount },
});
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(
@@ -100,78 +136,191 @@ describe('TransferForm', () => {
});
});
it('validates a manually entered address', async () => {
render(<TransferForm {...props} />);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
const toggle = screen.getByText('Enter manually');
fireEvent.click(toggle);
// has switched to input
expect(toggle).toHaveTextContent('Select from wallet');
expect(screen.getByLabelText('Vega key')).toHaveAttribute('type', 'text');
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: 'invalid-address' },
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Invalid Vega key');
describe('IncludeFeesCheckbox', () => {
it('validates fields and submits when checkbox is checked', async () => {
render(<TransferForm {...props} />);
// check current pubkey not shown
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
expect(keySelect.children).toHaveLength(2);
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
'',
props.pubKeys[1],
]);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
// Select a pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: props.pubKeys[1] },
});
// Select asset
fireEvent.change(
// Bypass RichSelect and target hidden native select
// eslint-disable-next-line
document.querySelector('select[name="asset"]')!,
{ target: { value: asset.id } }
);
// assert rich select as updated
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
asset.name
);
expect(screen.getByTestId('asset-balance')).toHaveTextContent(
formatNumber(asset.balance, asset.decimals)
);
const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked();
act(() => {
/* fire events that update state */
// set valid amount
fireEvent.change(amountInput, {
target: { value: amount },
});
// check include fees checkbox
fireEvent.click(checkbox);
});
expect(checkbox).toBeChecked();
const expectedFee = new BigNumber(amount)
.times(props.feeFactor)
.toFixed();
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
expectedAmount
);
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(
amount
);
submit();
await waitFor(() => {
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
expect(props.submitTransfer).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKeys[1],
asset: asset.id,
amount: removeDecimal(amount, asset.decimals),
oneOff: {},
});
});
});
// same pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: pubKey },
it('validates fields when checkbox is not checked', async () => {
render(<TransferForm {...props} />);
// check current pubkey not shown
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
expect(keySelect.children).toHaveLength(2);
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
'',
props.pubKeys[1],
]);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
// Select a pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: props.pubKeys[1] },
});
// Select asset
fireEvent.change(
// Bypass RichSelect and target hidden native select
// eslint-disable-next-line
document.querySelector('select[name="asset"]')!,
{ target: { value: asset.id } }
);
// assert rich select as updated
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
asset.name
);
expect(screen.getByTestId('asset-balance')).toHaveTextContent(
formatNumber(asset.balance, asset.decimals)
);
const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked();
act(() => {
/* fire events that update state */
// set valid amount
fireEvent.change(amountInput, {
target: { value: amount },
});
});
expect(checkbox).not.toBeChecked();
const expectedFee = new BigNumber(amount)
.times(props.feeFactor)
.toFixed();
const total = new BigNumber(amount).plus(expectedFee).toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
});
});
describe('AddressField', () => {
const props = {
pubKeys: ['pubkey-1', 'pubkey-2'],
select: <div>select</div>,
input: <div>input</div>,
onChange: jest.fn(),
};
it('toggles content and calls onChange', async () => {
const mockOnChange = jest.fn();
render(<AddressField {...props} onChange={mockOnChange} />);
// select should be shown as multiple pubkeys provided
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
fireEvent.click(screen.getByText('Enter manually'));
expect(screen.queryByText('select')).not.toBeInTheDocument();
expect(screen.getByText('input')).toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByText('Select from wallet'));
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(2);
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Vega key is the same');
it('Does not provide select option if there is only a single key', () => {
render(<AddressField {...props} pubKeys={['single-pubKey']} />);
expect(screen.getByText('input')).toBeInTheDocument();
expect(screen.queryByText('Select from wallet')).not.toBeInTheDocument();
});
});
describe('TransferFee', () => {
const props = {
amount: '200',
feeFactor: '0.001',
fee: '0.2',
transferAmount: '200',
decimals: 8,
};
it('calculates and renders the transfer fee', () => {
render(<TransferFee {...props} />);
const expected = new BigNumber(props.amount)
.times(props.feeFactor)
.toFixed();
const total = new BigNumber(props.amount).plus(expected).toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expected);
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
props.amount
);
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
});
});
});
describe('AddressField', () => {
const props = {
pubKeys: ['pubkey-1', 'pubkey-2'],
select: <div>select</div>,
input: <div>input</div>,
onChange: jest.fn(),
};
it('toggles content and calls onChange', async () => {
const mockOnChange = jest.fn();
render(<AddressField {...props} onChange={mockOnChange} />);
// select should be shown as multiple pubkeys provided
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
fireEvent.click(screen.getByText('Enter manually'));
expect(screen.queryByText('select')).not.toBeInTheDocument();
expect(screen.getByText('input')).toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByText('Select from wallet'));
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(2);
});
it('Does not provide select option if there is only a single key', () => {
render(<AddressField {...props} pubKeys={['single-pubKey']} />);
expect(screen.getByText('input')).toBeInTheDocument();
expect(screen.queryByText('Select from wallet')).not.toBeInTheDocument();
});
});
describe('TransferFee', () => {
const props = {
amount: '200',
feeFactor: '0.001',
};
it('calculates and renders the transfer fee', () => {
render(<TransferFee {...props} />);
const expected = new BigNumber(props.amount)
.times(props.feeFactor)
.toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expected);
});
});
+102 -16
View File
@@ -16,6 +16,7 @@ import {
RichSelect,
Select,
Tooltip,
Checkbox,
} from '@vegaprotocol/ui-toolkit';
import type { Transfer } from '@vegaprotocol/wallet';
import { normalizeTransfer } from '@vegaprotocol/wallet';
@@ -63,6 +64,26 @@ export const TransferForm = ({
const amount = watch('amount');
const assetId = watch('asset');
const [includeFee, setIncludeFee] = useState(false);
const transferAmount = useMemo(() => {
if (!amount) return undefined;
if (includeFee && feeFactor) {
return new BigNumber(1).minus(feeFactor).times(amount).toString();
}
return amount;
}, [amount, includeFee, feeFactor]);
const fee = useMemo(() => {
if (!transferAmount) return undefined;
if (includeFee) {
return new BigNumber(amount).minus(transferAmount).toString();
}
return (
feeFactor && new BigNumber(feeFactor).times(transferAmount).toString()
);
}, [amount, includeFee, transferAmount, feeFactor]);
const asset = useMemo(() => {
return assets.find((a) => a.id === assetId);
}, [assets, assetId]);
@@ -72,13 +93,16 @@ export const TransferForm = ({
if (!asset) {
throw new Error('Submitted transfer with no asset selected');
}
const transfer = normalizeTransfer(fields.toAddress, fields.amount, {
if (!transferAmount) {
throw new Error('Submitted transfer with no amount selected');
}
const transfer = normalizeTransfer(fields.toAddress, transferAmount, {
id: asset.id,
decimals: asset.decimals,
});
submitTransfer(transfer);
},
[asset, submitTransfer]
[asset, submitTransfer, transferAmount]
);
const min = useMemo(() => {
@@ -213,7 +237,32 @@ export const TransferForm = ({
<InputError forInput="amount">{errors.amount.message}</InputError>
)}
</FormGroup>
<TransferFee amount={amount} feeFactor={feeFactor} />
<div className="mb-4">
<Checkbox
name="include-transfer-fee"
disabled={!transferAmount}
label={
<Tooltip
description={t(
`The fee will be taken from the amount you are transferring.`
)}
>
<div>{t('Include transfer fee')}</div>
</Tooltip>
}
checked={includeFee}
onCheckedChange={() => setIncludeFee(!includeFee)}
/>
</div>
{transferAmount && fee && (
<TransferFee
amount={transferAmount}
transferAmount={transferAmount}
feeFactor={feeFactor}
fee={fee}
decimals={asset?.decimals}
/>
)}
<Button type="submit" variant="primary" fill={true}>
{t('Confirm transfer')}
</Button>
@@ -223,34 +272,71 @@ export const TransferForm = ({
export const TransferFee = ({
amount,
transferAmount,
feeFactor,
fee,
decimals,
}: {
amount: string;
transferAmount: string;
feeFactor: string | null;
fee?: string;
decimals?: number;
}) => {
if (!feeFactor || !amount) return null;
if (!feeFactor || !amount || !transferAmount || !fee) return null;
// using toFixed without an argument will always return a
// number in normal notation without rounding, formatting functions
// arent working in a way which won't round the decimal places
const value = new BigNumber(amount).times(feeFactor).toFixed();
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
return (
<div className="mb-4 flex justify-between items-center gap-4 flex-wrap">
<div>
<div className="mb-4 flex flex-col gap-2 text-xs">
<div className="flex justify-between gap-1 items-center flex-wrap">
<Tooltip
description={t(
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to ${feeFactor}`
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to %s`,
[feeFactor]
)}
>
<div>{t('Transfer fee')}</div>
</Tooltip>
<div
data-testid="transfer-fee"
className="text-neutral-500 dark:text-neutral-300"
>
{formatNumber(fee, decimals)}
</div>
</div>
<div
data-testid="transfer-fee"
className="text-neutral-500 dark:text-neutral-300"
>
{value}
<div className="flex justify-between gap-1 items-center flex-wrap">
<Tooltip
description={t(
`The total amount to be transferred (without the fee)`
)}
>
<div>{t('Amount to be transferred')}</div>
</Tooltip>
<div
data-testid="transfer-amount"
className="text-neutral-500 dark:text-neutral-300"
>
{formatNumber(amount, decimals)}
</div>
</div>
<div className="flex justify-between gap-1 items-center flex-wrap">
<Tooltip
description={t(
`The total amount taken from your account. The amount to be transferred plus the fee.`
)}
>
<div>{t('Total amount (with fee)')}</div>
</Tooltip>
<div
data-testid="total-transfer-fee"
className="text-neutral-500 dark:text-neutral-300"
>
{formatNumber(totalValue, decimals)}
</div>
</div>
</div>
);
+5
View File
@@ -1 +1,6 @@
import '@testing-library/jest-dom';
import ResizeObserver from 'resize-observer-polyfill';
import { defaultFallbackInView } from 'react-intersection-observer';
defaultFallbackInView(true);
global.ResizeObserver = ResizeObserver;
+18 -1
View File
@@ -1,8 +1,25 @@
fragment AssetListFields on Asset {
id
name
symbol
decimals
quantum
source {
__typename
... on ERC20 {
contractAddress
lifetimeLimit
withdrawThreshold
}
}
status
}
query Assets {
assetsConnection {
edges {
node {
...AssetFields
...AssetListFields
}
}
}
+23 -5
View File
@@ -1,26 +1,44 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import { AssetFieldsFragmentDoc } from './Asset';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type AssetListFieldsFragment = { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, quantum: string, status: Types.AssetStatus, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string, lifetimeLimit: string, withdrawThreshold: string } };
export type AssetsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type AssetsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, quantum: string, status: Types.AssetStatus, source: { __typename: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename: 'ERC20', contractAddress: string, lifetimeLimit: string, withdrawThreshold: string }, infrastructureFeeAccount?: { __typename?: 'AccountBalance', balance: string } | null, globalRewardPoolAccount?: { __typename?: 'AccountBalance', balance: string } | null, takerFeeRewardAccount?: { __typename?: 'AccountBalance', balance: string } | null, makerFeeRewardAccount?: { __typename?: 'AccountBalance', balance: string } | null, lpFeeRewardAccount?: { __typename?: 'AccountBalance', balance: string } | null, marketProposerRewardAccount?: { __typename?: 'AccountBalance', balance: string } | null } } | null> | null } | null };
export type AssetsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, quantum: string, status: Types.AssetStatus, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string, lifetimeLimit: string, withdrawThreshold: string } } } | null> | null } | null };
export const AssetListFieldsFragmentDoc = gql`
fragment AssetListFields on Asset {
id
name
symbol
decimals
quantum
source {
__typename
... on ERC20 {
contractAddress
lifetimeLimit
withdrawThreshold
}
}
status
}
`;
export const AssetsDocument = gql`
query Assets {
assetsConnection {
edges {
node {
...AssetFields
...AssetListFields
}
}
}
}
${AssetFieldsFragmentDoc}`;
${AssetListFieldsFragmentDoc}`;
/**
* __useAssetsQuery__
@@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react';
import * as Schema from '@vegaprotocol/types';
import { AssetDetailsDialog } from './asset-details-dialog';
import { AssetDetail, testId } from './asset-details-table';
import { AssetsDocument } from './__generated__/Assets';
import { AssetDocument } from './__generated__/Asset';
import { generateBuiltinAsset, generateERC20Asset } from './test-helpers';
const mockedData = {
@@ -39,15 +39,17 @@ const mockedData = {
},
};
const mocks = [
{
request: {
query: AssetsDocument,
variables: {},
},
result: mockedData,
const mocks = mockedData.data.assetsConnection.edges.map((mock) => ({
request: {
query: AssetDocument,
variables: { assetId: mock.node.id },
},
];
result: {
data: {
assetsConnection: { edges: [mock] },
},
},
}));
const WrappedAssetDetailsDialog = ({ assetId }: { assetId: string }) => (
<MockedProvider mocks={mocks}>
+2 -3
View File
@@ -1,5 +1,4 @@
import { t } from '@vegaprotocol/i18n';
import { useAssetsDataProvider } from './assets-data-provider';
import {
Button,
Dialog,
@@ -10,6 +9,7 @@ import {
import { create } from 'zustand';
import { AssetDetailsTable } from './asset-details-table';
import { AssetProposalNotification } from '@vegaprotocol/proposals';
import { useAssetDataProvider } from './asset-data-provider';
export type AssetDetailsDialogStore = {
isOpen: boolean;
@@ -55,9 +55,8 @@ export const AssetDetailsDialog = ({
onChange,
asJson = false,
}: AssetDetailsDialogProps) => {
const { data } = useAssetsDataProvider();
const { data: asset } = useAssetDataProvider(assetId);
const asset = data?.find((a) => a.id === assetId);
const assetSymbol = asset?.symbol || '';
const content = asset ? (
+2 -3
View File
@@ -11,6 +11,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import type { Asset } from './asset-data-provider';
import { WITHDRAW_THRESHOLD_TOOLTIP_TEXT } from './constants';
type Rows = {
key: AssetDetail;
@@ -121,9 +122,7 @@ export const rows: Rows = [
{
key: AssetDetail.WITHDRAWAL_THRESHOLD,
label: t('Withdrawal threshold'),
tooltip: t(
'The maximum you can withdraw instantly. Theres no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them'
),
tooltip: WITHDRAW_THRESHOLD_TOOLTIP_TEXT,
value: (asset) =>
num(asset, (asset.source as Schema.ERC20).withdrawThreshold),
},
+5 -119
View File
@@ -1,8 +1,10 @@
import merge from 'lodash/merge';
import type { AssetsQuery } from './__generated__/Assets';
import type {
AssetsQuery,
AssetListFieldsFragment,
} from './__generated__/Assets';
import * as Types from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest';
import type { AssetFieldsFragment } from './__generated__/Asset';
export const assetsQuery = (
override?: PartialDeep<AssetsQuery>
@@ -18,7 +20,7 @@ export const assetsQuery = (
return merge(defaultAssets, override);
};
const assetFields: AssetFieldsFragment[] = [
const assetFields: AssetListFieldsFragment[] = [
{
__typename: 'Asset',
id: 'asset-id',
@@ -33,30 +35,6 @@ const assetFields: AssetFieldsFragment[] = [
},
quantum: '1',
status: Types.AssetStatus.STATUS_ENABLED,
infrastructureFeeAccount: {
balance: '1',
__typename: 'AccountBalance',
},
globalRewardPoolAccount: {
balance: '2',
__typename: 'AccountBalance',
},
takerFeeRewardAccount: {
balance: '3',
__typename: 'AccountBalance',
},
makerFeeRewardAccount: {
balance: '4',
__typename: 'AccountBalance',
},
lpFeeRewardAccount: {
balance: '5',
__typename: 'AccountBalance',
},
marketProposerRewardAccount: {
balance: '6',
__typename: 'AccountBalance',
},
},
{
__typename: 'Asset',
@@ -72,30 +50,6 @@ const assetFields: AssetFieldsFragment[] = [
},
quantum: '1',
status: Types.AssetStatus.STATUS_ENABLED,
infrastructureFeeAccount: {
balance: '1',
__typename: 'AccountBalance',
},
globalRewardPoolAccount: {
balance: '2',
__typename: 'AccountBalance',
},
takerFeeRewardAccount: {
balance: '3',
__typename: 'AccountBalance',
},
makerFeeRewardAccount: {
balance: '4',
__typename: 'AccountBalance',
},
lpFeeRewardAccount: {
balance: '5',
__typename: 'AccountBalance',
},
marketProposerRewardAccount: {
balance: '6',
__typename: 'AccountBalance',
},
},
{
__typename: 'Asset',
@@ -104,20 +58,10 @@ const assetFields: AssetFieldsFragment[] = [
decimals: 5,
name: 'Asto',
source: {
maxFaucetAmountMint: '5000000000',
__typename: 'BuiltinAsset',
},
quantum: '1',
status: Types.AssetStatus.STATUS_ENABLED,
infrastructureFeeAccount: {
balance: '0',
__typename: 'AccountBalance',
},
globalRewardPoolAccount: null,
takerFeeRewardAccount: null,
makerFeeRewardAccount: null,
lpFeeRewardAccount: null,
marketProposerRewardAccount: null,
},
{
__typename: 'Asset',
@@ -126,20 +70,10 @@ const assetFields: AssetFieldsFragment[] = [
decimals: 5,
name: 'tBTC TEST',
source: {
maxFaucetAmountMint: '5000000000',
__typename: 'BuiltinAsset',
},
quantum: '1',
status: Types.AssetStatus.STATUS_ENABLED,
infrastructureFeeAccount: {
balance: '0',
__typename: 'AccountBalance',
},
globalRewardPoolAccount: null,
takerFeeRewardAccount: null,
makerFeeRewardAccount: null,
lpFeeRewardAccount: null,
marketProposerRewardAccount: null,
},
// NOTE: These assets ids and contract addresses are real assets on Sepolia, this is needed
// because we don't currently mock our seplia infura provider. If we change network these will
@@ -158,30 +92,6 @@ const assetFields: AssetFieldsFragment[] = [
__typename: 'ERC20',
},
quantum: '1',
infrastructureFeeAccount: {
balance: '1',
__typename: 'AccountBalance',
},
globalRewardPoolAccount: {
balance: '2',
__typename: 'AccountBalance',
},
takerFeeRewardAccount: {
balance: '3',
__typename: 'AccountBalance',
},
makerFeeRewardAccount: {
balance: '4',
__typename: 'AccountBalance',
},
lpFeeRewardAccount: {
balance: '5',
__typename: 'AccountBalance',
},
marketProposerRewardAccount: {
balance: '6',
__typename: 'AccountBalance',
},
},
{
__typename: 'Asset',
@@ -197,29 +107,5 @@ const assetFields: AssetFieldsFragment[] = [
__typename: 'ERC20',
},
quantum: '1',
infrastructureFeeAccount: {
balance: '1',
__typename: 'AccountBalance',
},
globalRewardPoolAccount: {
balance: '2',
__typename: 'AccountBalance',
},
takerFeeRewardAccount: {
balance: '3',
__typename: 'AccountBalance',
},
makerFeeRewardAccount: {
balance: '4',
__typename: 'AccountBalance',
},
lpFeeRewardAccount: {
balance: '5',
__typename: 'AccountBalance',
},
marketProposerRewardAccount: {
balance: '6',
__typename: 'AccountBalance',
},
},
];
+4
View File
@@ -0,0 +1,4 @@
import { t } from '@vegaprotocol/i18n';
export const WITHDRAW_THRESHOLD_TOOLTIP_TEXT = t(
"The maximum you can withdraw instantly. There's no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them"
);
+1
View File
@@ -5,3 +5,4 @@ export * from './assets-data-provider';
export * from './asset-details-dialog';
export * from './asset-details-table';
export * from './asset-option';
export * from './constants';
+2 -2
View File
@@ -1,6 +1,6 @@
import 'pennant/dist/style.css';
import {
Chart,
CandlestickChart,
ChartType,
Interval,
Overlay,
@@ -234,7 +234,7 @@ export const CandlesChartContainer = ({
</DropdownMenu>
</div>
<div className="flex-1">
<Chart
<CandlestickChart
dataSource={dataSource}
options={{
chartType: chartType,
+3
View File
@@ -52,6 +52,9 @@ export const checkSorting = (
cy.get(`[col-id="${column}"]`).click();
});
checkSortChange(orderTabDesc, column);
cy.get('.ag-header-container').within(() => {
cy.get(`[col-id="${column}"]`).click();
});
};
const checkSortChange = (tabsArr: string[], column: string) => {
@@ -1,6 +1,6 @@
import { memo } from 'react';
import { BID_COLOR, ASK_COLOR } from './vol-cell';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { addDecimalsFixedFormatNumber } from '@vegaprotocol/utils';
import { NumericCell } from './numeric-cell';
export interface CumulativeVolProps {
@@ -55,7 +55,7 @@ export const CumulativeVol = memo(
(
<NumericCell
value={Number(indicativeVolume)}
valueFormatted={addDecimalsFormatNumber(
valueFormatted={addDecimalsFixedFormatNumber(
indicativeVolume,
positionDecimalPlaces ?? 0
)}
@@ -67,7 +67,7 @@ export const CumulativeVol = memo(
{ask ? (
<NumericCell
value={ask}
valueFormatted={addDecimalsFormatNumber(
valueFormatted={addDecimalsFixedFormatNumber(
ask,
positionDecimalPlaces ?? 0
)}
@@ -77,7 +77,7 @@ export const CumulativeVol = memo(
{bid ? (
<NumericCell
value={ask}
valueFormatted={addDecimalsFormatNumber(
valueFormatted={addDecimalsFixedFormatNumber(
bid,
positionDecimalPlaces ?? 0
)}
@@ -21,7 +21,7 @@ export const MarketNameCell = ({
ev.preventDefault();
ev.stopPropagation();
if (onMarketClick) {
onMarketClick(id, ev.metaKey);
onMarketClick(id, ev.metaKey || ev.ctrlKey);
}
},
[id, onMarketClick]
@@ -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 (
<div>
{details.map(({ label, value, labelDescription, symbol }) => (
{details.map(({ label, value, labelDescription, symbol, indent }) => (
<div
key={typeof label === 'string' ? label : 'value-dropdown'}
className="text-xs mt-2 flex justify-between items-center gap-4 flex-wrap"
className={classnames(
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
{ 'ml-2': indent }
)}
>
<div>
<Tooltip description={labelDescription}>
@@ -107,7 +107,7 @@ describe('DealTicket', () => {
);
});
it('should use local storage state for initial values reduceOnly and postOnly', () => {
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,
@@ -115,7 +115,7 @@ describe('DealTicket', () => {
size: '0.1',
price: '300.22',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
persist: true,
persist: false,
reduceOnly: true,
postOnly: false,
};
@@ -149,6 +149,58 @@ describe('DealTicket', () => {
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 () => {
@@ -44,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;
@@ -103,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', {
@@ -158,6 +167,16 @@ export const DealTicket = ({
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();
@@ -202,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');
@@ -251,8 +280,23 @@ export const DealTicket = ({
value={order.timeInForce}
orderType={order.type}
onSelect={(timeInForce) => {
update({ timeInForce, postOnly: false, reduceOnly: false });
// 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,
@@ -327,6 +371,7 @@ export const DealTicket = ({
<Checkbox
name="reduce-only"
checked={order.reduceOnly}
disabled={disableReduceOnlyCheckbox}
onCheckedChange={() => {
update({ postOnly: false, reduceOnly: !order.reduceOnly });
}}
@@ -334,9 +379,13 @@ export const DealTicket = ({
<Tooltip
description={
<span>
{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.'
)}
{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.'
)}
</span>
}
>
@@ -367,9 +416,12 @@ export const DealTicket = ({
order={normalizedOrder}
market={market}
marketData={marketData}
margin={margin}
totalMargin={totalMargin}
balance={marginAccountBalance}
estimatedInitialMargin={margin}
estimatedTotalInitialMargin={totalMargin}
currentInitialMargin={currentMargins?.initialLevel}
currentMaintenanceMargin={currentMargins?.maintenanceLevel}
marginAccountBalance={marginAccountBalance}
generalAccountBalance={generalAccountBalance}
/>
</form>
</TinyScroll>
@@ -78,7 +78,9 @@ export const compileGridData = (
label: (
<Link
to={`/liquidity/${market.id}`}
onClick={(ev) => onSelect && onSelect(market.id, ev.metaKey)}
onClick={(ev) =>
onSelect && onSelect(market.id, ev.metaKey || ev.ctrlKey)
}
>
<UILink>{t('Current liquidity')}</UILink>
</Link>
+26 -2
View File
@@ -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 {
-4
View File
@@ -1,6 +1,2 @@
export * from './__generated__/EstimateOrder';
export * from './use-calculate-slippage';
export * from './use-fee-deal-ticket-details';
export * from './use-market-positions';
export * from './use-maximum-position-size';
export * from './use-order-closeout';
@@ -1,144 +0,0 @@
import { MockedProvider } from '@apollo/client/testing';
import { renderHook } from '@testing-library/react';
import * as Schema from '@vegaprotocol/types';
import type { Market } from '@vegaprotocol/market-list';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useCalculateSlippage } from './use-calculate-slippage';
const mockData = {
decimalPlaces: 0,
positionDecimalPlaces: 0,
depth: {
buy: [
{
price: '5',
volume: '2',
},
{
price: '4',
volume: '3',
},
{
price: '3',
volume: '2',
},
{
price: '2',
volume: '1',
},
{
price: '1',
volume: '1',
},
],
sell: [
{
price: '6',
volume: '1',
},
{
price: '7',
volume: '3',
},
{
price: '8',
volume: '2',
},
{
price: '9',
volume: '1',
},
{
price: '10',
volume: '2',
},
],
},
};
let mockOrderBookData = {
data: mockData,
};
jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/react-helpers'),
useDataProvider: jest.fn(() => ({
data: {
marketsConnection: [],
},
})),
useThrottledDataProvider: jest.fn(() => mockOrderBookData),
}));
describe('useCalculateSlippage Hook', () => {
describe('calculate proper result', () => {
afterEach(() => {
jest.clearAllMocks();
});
const market = {
id: 'marketId',
decimalPlaces: 0,
positionDecimalPlaces: 0,
} as Market;
it('long order', () => {
const { result } = renderHook(
() =>
useCalculateSlippage({
market,
order: {
size: '10',
side: Schema.Side.SIDE_BUY,
} as OrderSubmissionBody['orderSubmission'],
}),
{
wrapper: MockedProvider,
}
);
expect(result.current).toEqual('33.33');
});
it('short order', () => {
const { result } = renderHook(
() =>
useCalculateSlippage({
market,
order: {
size: '10',
side: Schema.Side.SIDE_SELL,
} as OrderSubmissionBody['orderSubmission'],
}),
{
wrapper: MockedProvider,
}
);
expect(result.current).toEqual('31.11');
});
it('when no order book result should be null', () => {
mockOrderBookData = {
data: {
...mockData,
depth: {
...mockData.depth,
buy: [],
},
},
};
const { result } = renderHook(
() =>
useCalculateSlippage({
market,
order: {
size: '10',
side: Schema.Side.SIDE_SELL,
} as OrderSubmissionBody['orderSubmission'],
}),
{
wrapper: MockedProvider,
}
);
expect(result.current).toBeNull();
});
});
});
@@ -1,63 +0,0 @@
import { marketDepthProvider } from '@vegaprotocol/market-depth';
import * as Schema from '@vegaprotocol/types';
import type { Market } from '@vegaprotocol/market-list';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { BigNumber } from 'bignumber.js';
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
import { useThrottledDataProvider } from '@vegaprotocol/react-helpers';
interface Props {
market: Market;
order: OrderSubmissionBody['orderSubmission'];
}
export const useCalculateSlippage = ({ market, order }: Props) => {
const { data } = useThrottledDataProvider(
{
dataProvider: marketDepthProvider,
variables: { marketId: market.id },
},
1000
);
const volPriceArr =
data?.depth[order.side === Schema.Side.SIDE_BUY ? 'sell' : 'buy'] || [];
if (volPriceArr.length && market) {
const decimals = market.decimalPlaces ?? 0;
const positionDecimals = market.positionDecimalPlaces ?? 0;
const bestPrice = toBigNum(volPriceArr[0].price, decimals);
const { size } = order;
let descSize = new BigNumber(size);
let i = 0;
const volPricePairs: Array<[BigNumber, BigNumber]> = [];
while (!descSize.isZero() && i < volPriceArr.length) {
const price = toBigNum(volPriceArr[i].price, decimals);
const amount = BigNumber.min(
descSize,
toBigNum(volPriceArr[i].volume, positionDecimals)
);
volPricePairs.push([price, amount]);
descSize = BigNumber.max(0, descSize.minus(amount));
i++;
}
if (volPricePairs.length) {
const volWeightAvPricePair = volPricePairs.reduce(
(agg, item) => {
agg[0] = agg[0].plus(item[0].multipliedBy(item[1]));
agg[1] = agg[1].plus(item[1]);
return agg;
},
[new BigNumber(0), new BigNumber(0)]
);
const volWeightAvPrice = volWeightAvPricePair[0].dividedBy(
volWeightAvPricePair[1]
);
const slippage = volWeightAvPrice
.minus(bestPrice)
.absoluteValue()
.dividedBy(bestPrice)
.multipliedBy(100);
return formatNumber(slippage, 2);
}
}
return null;
};
@@ -15,8 +15,9 @@ 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';
import { getDerivedPrice } from '../utils/get-price';
import { useEstimateOrderQuery } from './__generated__/EstimateOrder';
@@ -47,12 +48,6 @@ export const useFeeDealTicketDetails = (
skip: !pubKey || !market || !order.size || !price,
});
const estCloseOut = useOrderCloseOut({
order,
market,
marketData,
});
const notionalSize = useMemo(() => {
if (price && order.size) {
return toBigNum(order.size, market.positionDecimalPlaces)
@@ -72,37 +67,36 @@ export const useFeeDealTicketDetails = (
notionalSize,
accountBalance,
estimateOrder: estMargin?.estimateOrder,
estCloseOut,
};
}, [
market,
assetSymbol,
notionalSize,
accountBalance,
estMargin,
estCloseOut,
]);
}, [market, assetSymbol, notionalSize, accountBalance, estMargin]);
};
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 +117,8 @@ export const getFeeDetailsValues = ({
label: string;
value?: string | null;
symbol: string;
labelDescription: React.ReactNode;
indent?: boolean;
labelDescription?: React.ReactNode;
}[] = [
{
label: t('Notional'),
@@ -153,38 +148,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,
});
@@ -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]
);
};
@@ -1,53 +0,0 @@
import { renderHook } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { useMarketPositions } from './use-market-positions';
jest.mock('@vegaprotocol/wallet', () => ({
...jest.requireActual('@vegaprotocol/wallet'),
useVegaWallet: jest.fn().mockReturnValue('wallet-pub-key'),
}));
let mockMarketAccountBalance: {
accountBalance: string;
accountDecimals: number | null;
} = { accountBalance: '50001000000', accountDecimals: 5 };
jest.mock('@vegaprotocol/accounts', () => ({
...jest.requireActual('@vegaprotocol/accounts'),
useMarketAccountBalance: jest.fn(() => mockMarketAccountBalance),
}));
jest.mock('@vegaprotocol/positions', () => ({
...jest.requireActual('@vegaprotocol/positions'),
useMarketPositionOpenVolume: jest.fn(() => '100002'),
}));
describe('useOrderPosition Hook', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should return proper positive value', () => {
const { result } = renderHook(
() => useMarketPositions({ marketId: 'marketId' }),
{ wrapper: MockedProvider }
);
expect(result.current?.openVolume).toEqual('100002');
expect(result.current?.balance).toEqual('50001000000');
});
it('if balance equal 0 return null', () => {
mockMarketAccountBalance = { accountBalance: '0', accountDecimals: 5 };
const { result } = renderHook(
() => useMarketPositions({ marketId: 'marketId' }),
{ wrapper: MockedProvider }
);
expect(result.current).toBeNull();
});
it('if no markets return null', () => {
mockMarketAccountBalance = { accountBalance: '', accountDecimals: null };
const { result } = renderHook(
() => useMarketPositions({ marketId: 'marketId' }),
{ wrapper: MockedProvider }
);
expect(result.current).toBeNull();
});
});
@@ -1,34 +0,0 @@
import { useMemo } from 'react';
import { BigNumber } from 'bignumber.js';
import { useMarketAccountBalance } from '@vegaprotocol/accounts';
import { useMarketPositionOpenVolume } from '@vegaprotocol/positions';
interface Props {
marketId: string;
}
export type PositionMargin = {
openVolume: string;
balance: string;
balanceDecimals?: number;
} | null;
export const useMarketPositions = ({ marketId }: Props): PositionMargin => {
const { accountBalance, accountDecimals } = useMarketAccountBalance(marketId);
const openVolume = useMarketPositionOpenVolume(marketId);
return useMemo(() => {
if (accountBalance && accountDecimals) {
const balance = new BigNumber(accountBalance);
const volume = new BigNumber(openVolume);
if (!balance.isZero() && !volume.isZero()) {
return {
balance: accountBalance,
balanceDecimals: accountDecimals,
openVolume,
};
}
}
return null;
}, [accountBalance, accountDecimals, openVolume]);
};
@@ -1,120 +0,0 @@
import { renderHook } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import * as Schema from '@vegaprotocol/types';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { PositionMargin } from './use-market-positions';
import { useMaximumPositionSize } from './use-maximum-position-size';
jest.mock('@vegaprotocol/wallet', () => ({
...jest.requireActual('@vegaprotocol/wallet'),
useVegaWallet: jest.fn().mockReturnValue('wallet-pub-key'),
}));
let mockAccountBalance: {
accountBalance: string;
accountDecimals: number | null;
} = { accountBalance: '200000', accountDecimals: 5 };
jest.mock('@vegaprotocol/accounts', () => ({
...jest.requireActual('@vegaprotocol/accounts'),
useAccountBalance: jest.fn(() => mockAccountBalance),
}));
const defaultMockMarketPositions = {
openVolume: '1',
balance: '100000',
};
let mockMarketPositions: PositionMargin | null = defaultMockMarketPositions;
const mockOrder: OrderSubmissionBody['orderSubmission'] = {
type: Schema.OrderType.TYPE_MARKET,
size: '1',
side: Schema.Side.SIDE_BUY,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
marketId: 'market-id',
};
jest.mock('./use-market-positions', () => ({
useMarketPositions: ({
marketId,
partyId,
}: {
marketId: string;
partyId: string;
}) => mockMarketPositions,
}));
describe('useMaximumPositionSize', () => {
it('should return correct size when no open positions', () => {
mockMarketPositions = null;
const price = '50';
const expected = 4000;
const { result } = renderHook(
() =>
useMaximumPositionSize({
marketId: '',
price,
settlementAssetId: '',
order: mockOrder,
}),
{ wrapper: MockedProvider }
);
expect(result.current).toBe(expected);
});
it('should return correct size when open positions and same side', () => {
const price = '50';
mockMarketPositions = defaultMockMarketPositions;
const expected = 3999;
const { result } = renderHook(
() =>
useMaximumPositionSize({
marketId: '',
price,
settlementAssetId: '',
order: mockOrder,
}),
{ wrapper: MockedProvider }
);
expect(result.current).toBe(expected);
});
it('should return correct size when open positions and opposite side', () => {
const price = '50';
mockOrder.side = Schema.Side.SIDE_SELL;
mockMarketPositions = defaultMockMarketPositions;
const expected = 4001;
const { result } = renderHook(
() =>
useMaximumPositionSize({
marketId: '',
price,
settlementAssetId: '',
order: mockOrder,
}),
{ wrapper: MockedProvider }
);
expect(result.current).toBe(expected);
});
it('should return zero if no account balance', () => {
mockAccountBalance = {
accountBalance: '0',
accountDecimals: 5,
};
const price = '50';
mockMarketPositions = defaultMockMarketPositions;
const expected = 0;
const { result } = renderHook(
() =>
useMaximumPositionSize({
marketId: '',
price,
settlementAssetId: '',
order: mockOrder,
}),
{ wrapper: MockedProvider }
);
expect(result.current).toBe(expected);
});
});
@@ -1,46 +0,0 @@
import * as Schema from '@vegaprotocol/types';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useAccountBalance } from '@vegaprotocol/accounts';
import { BigNumber } from 'bignumber.js';
import { useMarketPositions } from './use-market-positions';
interface Props {
marketId: string;
price?: string;
settlementAssetId: string;
order: OrderSubmissionBody['orderSubmission'];
}
const getSize = (balance: string, price: string) =>
new BigNumber(balance).dividedBy(new BigNumber(price));
export const useMaximumPositionSize = ({
marketId,
price,
settlementAssetId,
order,
}: Props): number => {
const { accountBalance } = useAccountBalance(settlementAssetId) || {};
const marketPositions = useMarketPositions({ marketId: marketId });
if (!accountBalance || new BigNumber(accountBalance || 0).isZero()) {
return 0;
}
const size = getSize(accountBalance, price || '');
if (!marketPositions) {
return size.toNumber() || 0;
}
const isSameSide =
(new BigNumber(marketPositions.openVolume).isPositive() &&
order.side === Schema.Side.SIDE_BUY) ||
(new BigNumber(marketPositions.openVolume).isNegative() &&
order.side === Schema.Side.SIDE_SELL);
const adjustedForVolume = new BigNumber(size)[isSameSide ? 'minus' : 'plus'](
marketPositions.openVolume
);
return adjustedForVolume.isNegative() ? 0 : adjustedForVolume.toNumber();
};

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