Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4998317bd2 | ||
|
|
e4dbe11a03 | ||
|
|
b7645773b8 | ||
|
|
7ec1f2f367 | ||
|
|
f2d6297fa5 | ||
|
|
6f2cc46a77 | ||
|
|
73862998c7 | ||
|
|
1e222aba2d | ||
|
|
61c5a7ba68 |
@@ -5,10 +5,7 @@ on:
|
||||
branches:
|
||||
- release/*
|
||||
- develop
|
||||
- main
|
||||
# uncomment pull_request and comment pull_request_target to test CI changes against feature branch not target branch (develop)
|
||||
# pull_request:
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- ready_for_review
|
||||
@@ -49,7 +46,7 @@ jobs:
|
||||
|
||||
lint-pr-title:
|
||||
needs: node-modules
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
name: Verify PR title
|
||||
uses: ./.github/workflows/lint-pr.yml
|
||||
secrets: inherit
|
||||
@@ -180,6 +177,24 @@ jobs:
|
||||
fi
|
||||
fi
|
||||
|
||||
# if branch starts with release/ and ends with trading / governance or explorer - overwrite the array of affected projects with fixed single application
|
||||
if [[ "${{ github.ref }}" == release* ]]; then
|
||||
case "${{ github.ref }}" in
|
||||
*trading)
|
||||
projects_array=(trading)
|
||||
projects_e2e_array=(trading)
|
||||
;;
|
||||
*governance)
|
||||
projects_array=(governance)
|
||||
projects_e2e_array=(governance)
|
||||
;;
|
||||
*explorer)
|
||||
projects_array=(explorer)
|
||||
projects_e2e_array=(explorer)
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "Projects: ${projects_array[@]}"
|
||||
echo "Projects E2E: ${projects_e2e_array[@]}"
|
||||
projects_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_array[@]}")
|
||||
@@ -214,7 +229,7 @@ jobs:
|
||||
publish-dist:
|
||||
needs: lint-test-build
|
||||
name: '(CD) publish dist'
|
||||
# if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
|
||||
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo') || github.event_name == 'push' }}
|
||||
uses: ./.github/workflows/publish-dist.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -225,7 +240,7 @@ jobs:
|
||||
needs:
|
||||
- publish-dist
|
||||
- lint-test-build
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }}
|
||||
timeout-minutes: 60
|
||||
name: '(CD) comment preview links'
|
||||
steps:
|
||||
@@ -271,7 +286,7 @@ jobs:
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Previews:
|
||||
Previews
|
||||
* governance: ${{ needs.lint-test-build.outputs.preview_governance }}
|
||||
* explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
|
||||
* trading: ${{ needs.lint-test-build.outputs.preview_trading }}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
name: console-test-run
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
github-sha:
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
console-test:
|
||||
timeout-minutes: 5
|
||||
runs-on: self-hosted-runner
|
||||
steps:
|
||||
- name: Checkout console test repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: vegaprotocol/console-test
|
||||
path: './console-test'
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10.11'
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
poetry install --no-root
|
||||
working-directory: ./console-test
|
||||
|
||||
- name: load Binaries
|
||||
run: |
|
||||
poetry run python -m vega_sim.tools.load_binaries
|
||||
working-directory: ./console-test
|
||||
|
||||
- name: pull console
|
||||
run: |
|
||||
poetry run docker pull ghcr.io/vegaprotocol/frontend/trading:${{ inputs.github-sha }}
|
||||
|
||||
- name: Update container_name in config.py
|
||||
run: |
|
||||
sed -i "s/container_name = \".*\"/container_name = \"vegaprotocol\/frontend\/trading:${{ inputs.github-sha }}\"/g" config.py
|
||||
|
||||
- name: install playwright
|
||||
run: poetry run playwright install
|
||||
working-directory: ./console-test
|
||||
|
||||
- name: run tests
|
||||
run: poetry run pytest --numprocesses auto
|
||||
working-directory: ./console-test
|
||||
|
||||
- name: Upload Playwright Trace
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-trace
|
||||
path: ./traces/
|
||||
retention-days: 15
|
||||
@@ -22,6 +22,33 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Init variables
|
||||
run: |
|
||||
echo IS_PR=false >> $GITHUB_ENV
|
||||
echo IS_MAINNET_RELEASE=false >> $GITHUB_ENV
|
||||
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
|
||||
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
|
||||
|
||||
- name: Is PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo IS_PR=true >> $GITHUB_ENV
|
||||
|
||||
- name: Is mainnet release
|
||||
if: ${{ contains(github.ref, 'release/mainnnet') && !contains(github.ref, 'mirror') }}
|
||||
run: |
|
||||
echo IS_MAINNET_RELEASE=true >> $GITHUB_ENV
|
||||
|
||||
- name: Is testnet release
|
||||
if: ${{ contains(github.ref, 'release/testnet') }}
|
||||
run: |
|
||||
echo IS_TESTNET_RELEASE=true >> $GITHUB_ENV
|
||||
|
||||
- name: Is IPFS Release
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( env.IS_MAINNET_RELEASE == 'true' || env.IS_TESTNET_RELEASE == 'true' ) }}
|
||||
run: |
|
||||
echo IS_IPFS_RELEASE=true >> $GITHUB_ENV
|
||||
|
||||
- name: Set up QEMU
|
||||
id: quemu
|
||||
uses: docker/setup-qemu-action@v2
|
||||
@@ -33,7 +60,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to the Container registry (ghcr)
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
if: ${{ env.IS_PR == 'true' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -42,9 +69,8 @@ jobs:
|
||||
|
||||
- name: Log in to the Container registry (docker hub)
|
||||
uses: docker/login-action@v2
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
||||
with:
|
||||
# registry: registry.hub.docker.com
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
@@ -70,7 +96,8 @@ jobs:
|
||||
bucketName=''
|
||||
|
||||
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
|
||||
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
|
||||
# remove prefixing release/ and take the first string limited by - which is supposed to be name of the environment for releasing (format: release/testnet-trading)
|
||||
envName="$(echo ${{ github.ref }} | sed -e "s|release/||" | cut -d '-' -f 1 )"
|
||||
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
|
||||
envName="stagnet1"
|
||||
if [[ "${{ matrix.app }}" = "multisig-signer" ]]; then
|
||||
@@ -145,7 +172,7 @@ jobs:
|
||||
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
|
||||
|
||||
- name: Image digest
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
if: ${{ env.IS_PR == 'true' }}
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
|
||||
- name: Sanity check docker image
|
||||
@@ -160,7 +187,7 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
continue-on-error: true
|
||||
id: ghcr-push
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
if: ${{ env.IS_PR == 'true' }}
|
||||
with:
|
||||
context: .
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
@@ -175,7 +202,7 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
continue-on-error: true
|
||||
id: dockerhub-push
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
||||
with:
|
||||
context: .
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
@@ -185,7 +212,7 @@ jobs:
|
||||
ENV_NAME=${{ env.ENV_NAME }}
|
||||
tags: |
|
||||
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
|
||||
vegaprotocol/${{ matrix.app }}:${{ endsWith(github.ref, 'main') && 'mainnet' || endsWith(github.ref, 'release/testnet') && 'testnet' || '' }}
|
||||
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
|
||||
|
||||
- name: Publish dist as docker image (ghcr - retry)
|
||||
uses: docker/build-push-action@v3
|
||||
@@ -212,13 +239,13 @@ jobs:
|
||||
ENV_NAME=${{ env.ENV_NAME }}
|
||||
tags: |
|
||||
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
|
||||
vegaprotocol/${{ matrix.app }}:${{ endsWith(github.ref, 'main') && 'mainnet' || endsWith(github.ref, 'release/testnet') && 'testnet' || '' }}
|
||||
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
|
||||
|
||||
# bucket creation in github.com/vegaprotocol/terraform//frontend
|
||||
- name: Publish dist to s3
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
# s3 releases are not happening for trading on mainnet - it's IPFS
|
||||
if: ${{ github.event_name == 'push' && ( matrix.app != 'trading' || (matrix.app == 'trading' && !( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) ) ) }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'false' }}
|
||||
with:
|
||||
args: --acl private --follow-symlinks --delete
|
||||
env:
|
||||
@@ -229,11 +256,11 @@ jobs:
|
||||
SOURCE_DIR: 'dist-result'
|
||||
|
||||
- name: Install aws CLI
|
||||
if: ${{ github.event_name == 'push' && ( matrix.app != 'trading' || (matrix.app == 'trading' && !( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) ) ) }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'false' }}
|
||||
uses: unfor19/install-aws-cli-action@master
|
||||
|
||||
- name: Perform cache invalidation
|
||||
if: ${{ github.event_name == 'push' && ( matrix.app != 'trading' || (matrix.app == 'trading' && !( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) ) ) }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'false' }}
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
@@ -246,14 +273,14 @@ jobs:
|
||||
|
||||
- name: Add preview label
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
if: ${{ env.IS_PR == 'true' }}
|
||||
with:
|
||||
labels: ${{ matrix.app }}-preview
|
||||
number: ${{ github.event.number }}
|
||||
|
||||
- name: Trigger fleek deployment
|
||||
# release to ipfs happens only on mainnet (represented by main branch) for trading
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
||||
run: |
|
||||
if echo ${{ github.ref }} | grep -q main; then
|
||||
# display info about app
|
||||
@@ -283,7 +310,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Check out ipfs-redirect
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'testnet') ) }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'vegaprotocol/ipfs-redirect'
|
||||
@@ -292,7 +319,7 @@ jobs:
|
||||
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
|
||||
|
||||
- name: Update interstitial page to point to the new console
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'testnet') ) }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
|
||||
run: |
|
||||
@@ -314,11 +341,11 @@ jobs:
|
||||
git config --global user.name "vega-ci-bot"
|
||||
|
||||
# update CID files
|
||||
if echo ${{ github.ref }} | grep -q main; then
|
||||
if [[ "${{ env.IS_MAINNET_RELEASE }}" = "true" ]]; then
|
||||
echo $new_hash > cidv0-mainnet.txt
|
||||
echo $new_cid > cidv1-mainnet.txt
|
||||
git add cidv0-mainnet.txt cidv1-mainnet.txt
|
||||
elif echo ${{ github.ref }} | grep -q release/testnet; then
|
||||
elif [[ "${{ env.IS_TESTNET_RELEASE }}" = "true" ]]; then
|
||||
echo $new_hash > cidv0-fairground.txt
|
||||
echo $new_cid > cidv1-fairground.txt
|
||||
git add cidv0-fairground.txt cidv1-fairground.txt
|
||||
|
||||
@@ -13,12 +13,6 @@ export const VegaWalletDialogs = () => {
|
||||
<>
|
||||
<VegaConnectDialog
|
||||
connectors={Connectors}
|
||||
onChangeOpen={(open) =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY,
|
||||
isOpen: open,
|
||||
})
|
||||
}
|
||||
riskMessage={<RiskMessage />}
|
||||
/>
|
||||
|
||||
|
||||
@@ -2,15 +2,18 @@ import {
|
||||
RestConnector,
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
InjectedConnector,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
export const rest = new RestConnector();
|
||||
export const jsonRpc = new JsonRpcConnector();
|
||||
export const injected = new InjectedConnector();
|
||||
export const view = new ViewConnector(urlParams.get('address'));
|
||||
|
||||
export const Connectors = {
|
||||
injected,
|
||||
rest,
|
||||
jsonRpc,
|
||||
view,
|
||||
|
||||
@@ -8,9 +8,10 @@ import { TxState } from '../../../hooks/transaction-reducer';
|
||||
import { useTransaction } from '../../../hooks/use-transaction';
|
||||
import { BigNumber } from '../../../lib/bignumber';
|
||||
import { AssociateInfo } from './associate-info';
|
||||
import { removeDecimal, toBigNum } from '@vegaprotocol/utils';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import type { EthereumConfig } from '@vegaprotocol/web3';
|
||||
import { useBalances } from '../../../lib/balances/balances-store';
|
||||
import { MaxUint256 } from '@ethersproject/constants';
|
||||
|
||||
export const WalletAssociate = ({
|
||||
perform,
|
||||
@@ -42,7 +43,7 @@ export const WalletAssociate = ({
|
||||
} = useTransaction(() => {
|
||||
return token.approve(
|
||||
ethereumConfig.staking_bridge_contract.address,
|
||||
removeDecimal('1000000', decimals).toString()
|
||||
MaxUint256.toString()
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -14,4 +14,4 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.20.21-core-0.71.6
|
||||
NX_APP_VERSION=v0.20.22-core-0.71.6
|
||||
|
||||
@@ -2,10 +2,12 @@ import {
|
||||
RestConnector,
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
InjectedConnector,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
export const rest = new RestConnector();
|
||||
export const jsonRpc = new JsonRpcConnector();
|
||||
export const injected = new InjectedConnector();
|
||||
|
||||
let view: ViewConnector;
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -16,6 +18,7 @@ if (typeof window !== 'undefined') {
|
||||
}
|
||||
|
||||
export const Connectors = {
|
||||
injected,
|
||||
rest,
|
||||
jsonRpc,
|
||||
view,
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Position } from './positions-data-providers';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { PositionStatus, PositionStatusMapping } from '@vegaprotocol/types';
|
||||
import type { ICellRendererParams } from 'ag-grid-community';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
|
||||
jest.mock('./liquidation-price', () => ({
|
||||
LiquidationPrice: () => (
|
||||
@@ -19,7 +20,7 @@ const singleRow: Position = {
|
||||
assetSymbol: 'BTC',
|
||||
averageEntryPrice: '133',
|
||||
currentLeverage: 1.1,
|
||||
decimals: 2,
|
||||
decimals: 2, // this is settlementAsset.decimals
|
||||
quantum: '0.1',
|
||||
lossSocializationAmount: '0',
|
||||
marginAccountBalance: '12345600',
|
||||
@@ -177,12 +178,22 @@ it('displays allocated margin', async () => {
|
||||
});
|
||||
|
||||
it('displays realised and unrealised PNL', async () => {
|
||||
// pnl cells should be rendered with asset dps
|
||||
const expectedRealised = addDecimalsFormatNumber(
|
||||
singleRow.realisedPNL,
|
||||
singleRow.decimals
|
||||
);
|
||||
const expectedUnrealised = addDecimalsFormatNumber(
|
||||
singleRow.unrealisedPNL,
|
||||
singleRow.decimals
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[9].textContent).toEqual('12.3');
|
||||
expect(cells[10].textContent).toEqual('45.6');
|
||||
expect(cells[9].textContent).toEqual(expectedRealised);
|
||||
expect(cells[10].textContent).toEqual(expectedUnrealised);
|
||||
});
|
||||
|
||||
it('displays close button', async () => {
|
||||
|
||||
@@ -365,20 +365,14 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data.realisedPNL,
|
||||
data.marketDecimalPlaces
|
||||
).toNumber();
|
||||
: toBigNum(data.realisedPNL, data.decimals).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'realisedPNL'>) => {
|
||||
return !data
|
||||
? ''
|
||||
: addDecimalsFormatNumber(
|
||||
data.realisedPNL,
|
||||
data.marketDecimalPlaces
|
||||
);
|
||||
: addDecimalsFormatNumber(data.realisedPNL, data.decimals);
|
||||
},
|
||||
headerTooltip: t(
|
||||
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
|
||||
@@ -396,20 +390,14 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data.unrealisedPNL,
|
||||
data.marketDecimalPlaces
|
||||
).toNumber();
|
||||
: toBigNum(data.unrealisedPNL, data.decimals).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'unrealisedPNL'>) =>
|
||||
!data
|
||||
? ''
|
||||
: addDecimalsFormatNumber(
|
||||
data.unrealisedPNL,
|
||||
data.marketDecimalPlaces
|
||||
),
|
||||
: addDecimalsFormatNumber(data.unrealisedPNL, data.decimals),
|
||||
headerTooltip: t(
|
||||
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
|
||||
),
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const IconChevronLeft = ({ size = 16 }: { size: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 16 16">
|
||||
<path d="M10.38 1.62L11.13 2.38L5.5 8L11.13 13.62L10.38 14.38L4 8L10.38 1.62Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { IconArrowDown } from './svg-icons/icon-arrow-down';
|
||||
import { IconArrowRight } from './svg-icons/icon-arrow-right';
|
||||
import { IconBreakdown } from './svg-icons/icon-breakdown';
|
||||
import { IconChevronDown } from './svg-icons/icon-chevron-down';
|
||||
import { IconChevronLeft } from './svg-icons/icon-chevron-left';
|
||||
import { IconChevronUp } from './svg-icons/icon-chevron-up';
|
||||
import { IconCopy } from './svg-icons/icon-copy';
|
||||
import { IconCross } from './svg-icons/icon-cross';
|
||||
@@ -26,6 +27,7 @@ export enum VegaIconNames {
|
||||
ARROW_RIGHT = 'arrow-right',
|
||||
BREAKDOWN = 'breakdown',
|
||||
CHEVRON_DOWN = 'chevron-down',
|
||||
CHEVRON_LEFT = 'chevron-left',
|
||||
CHEVRON_UP = 'chevron-up',
|
||||
COPY = 'copy',
|
||||
CROSS = 'cross',
|
||||
@@ -53,6 +55,7 @@ export const VegaIconNameMap: Record<
|
||||
'arrow-down': IconArrowDown,
|
||||
'arrow-right': IconArrowRight,
|
||||
'chevron-down': IconChevronDown,
|
||||
'chevron-left': IconChevronLeft,
|
||||
'chevron-up': IconChevronUp,
|
||||
'open-external': IconOpenExternal,
|
||||
'question-mark': IconQuestionMark,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { VegaConnector } from '../connectors';
|
||||
import { RestConnector } from '../connectors';
|
||||
|
||||
export const ConnectDialogTitle = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
@@ -18,11 +21,35 @@ export const ConnectDialogContent = ({ children }: { children: ReactNode }) => {
|
||||
return <div>{children}</div>;
|
||||
};
|
||||
|
||||
export const ConnectDialogFooter = ({ children }: { children?: ReactNode }) => {
|
||||
export const ConnectDialogFooter = ({
|
||||
connector,
|
||||
}: {
|
||||
connector: VegaConnector | undefined;
|
||||
}) => {
|
||||
const wrapperClasses = classNames(
|
||||
'flex justify-center gap-4',
|
||||
'px-4 md:px-8 pt-4 md:pt-6',
|
||||
'border-t border-vega-light-200 dark:border-vega-dark-200',
|
||||
'text-vega-light-400 dark:text-vega-dark-400'
|
||||
);
|
||||
const isHostedWalletSelected = connector instanceof RestConnector;
|
||||
return (
|
||||
<footer className="flex justify-center gap-4 px-4 md:px-8 pt-4 md:pt-6 -mx-4 md:-mx-8 border-t border-neutral-500 text-neutral-500 dark:text-neutral-400 mt-6">
|
||||
{children ? (
|
||||
children
|
||||
<footer className={wrapperClasses}>
|
||||
{isHostedWalletSelected ? (
|
||||
<p className="text-center">
|
||||
{t('For demo purposes get a ')}
|
||||
<Link
|
||||
href={ExternalLinks.VEGA_WALLET_HOSTED_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t('hosted wallet')}
|
||||
</Link>
|
||||
{t(', or for the real experience create a wallet in the ')}
|
||||
<Link href={ExternalLinks.VEGA_WALLET_URL}>
|
||||
{t('Vega wallet app')}
|
||||
</Link>
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<Link href={ExternalLinks.VEGA_WALLET_URL}>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import type { VegaConnectDialogProps } from '..';
|
||||
import {
|
||||
ClientErrors,
|
||||
InjectedConnector,
|
||||
JsonRpcConnector,
|
||||
RestConnector,
|
||||
ViewConnector,
|
||||
@@ -24,6 +25,12 @@ import {
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ChainIdQuery } from './__generated__/ChainId';
|
||||
import { ChainIdDocument } from './__generated__/ChainId';
|
||||
import {
|
||||
mockBrowserWallet,
|
||||
clearBrowserWallet,
|
||||
delayedReject,
|
||||
delayedResolve,
|
||||
} from '../test-helpers';
|
||||
|
||||
const mockUpdateDialogOpen = jest.fn();
|
||||
const mockCloseVegaDialog = jest.fn();
|
||||
@@ -49,10 +56,12 @@ const INITIAL_KEY = 'some-key';
|
||||
const rest = new RestConnector();
|
||||
const jsonRpc = new JsonRpcConnector();
|
||||
const view = new ViewConnector(INITIAL_KEY);
|
||||
const injected = new InjectedConnector();
|
||||
const connectors = {
|
||||
rest,
|
||||
jsonRpc,
|
||||
view,
|
||||
injected,
|
||||
};
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -105,7 +114,7 @@ describe('VegaConnectDialog', () => {
|
||||
expect(screen.getByTestId('connector-jsonRpc')).toHaveTextContent(
|
||||
'Connect Vega wallet'
|
||||
);
|
||||
expect(screen.getByTestId('connector-hosted')).toHaveTextContent(
|
||||
expect(screen.getByTestId('connector-rest')).toHaveTextContent(
|
||||
'Hosted Fairground wallet'
|
||||
);
|
||||
expect(screen.getByTestId('connector-view')).toHaveTextContent(
|
||||
@@ -113,6 +122,17 @@ describe('VegaConnectDialog', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('displays browser wallet option if detected on window object', async () => {
|
||||
mockBrowserWallet();
|
||||
render(generateJSX());
|
||||
const list = await screen.findByTestId('connectors-list');
|
||||
expect(list.children).toHaveLength(4);
|
||||
expect(screen.getByTestId('connector-injected')).toHaveTextContent(
|
||||
'Connect Web wallet'
|
||||
);
|
||||
clearBrowserWallet();
|
||||
});
|
||||
|
||||
describe('RestConnector', () => {
|
||||
it('connects', async () => {
|
||||
const spy = jest
|
||||
@@ -229,17 +249,19 @@ describe('VegaConnectDialog', () => {
|
||||
beforeEach(() => {
|
||||
spyOnCheckCompat = jest
|
||||
.spyOn(connectors.jsonRpc, 'checkCompat')
|
||||
.mockImplementation(() => delayedResolve(true));
|
||||
.mockImplementation(() => delayedResolve(true, delay));
|
||||
spyOnGetChainId = jest
|
||||
.spyOn(connectors.jsonRpc, 'getChainId')
|
||||
.mockImplementation(() => delayedResolve({ chainID: mockChainId }));
|
||||
.mockImplementation(() =>
|
||||
delayedResolve({ chainID: mockChainId }, delay)
|
||||
);
|
||||
spyOnConnectWallet = jest
|
||||
.spyOn(connectors.jsonRpc, 'connectWallet')
|
||||
.mockImplementation(() => delayedResolve(null));
|
||||
.mockImplementation(() => delayedResolve(null, delay));
|
||||
spyOnConnect = jest
|
||||
.spyOn(connectors.jsonRpc, 'connect')
|
||||
.mockImplementation(() =>
|
||||
delayedResolve([{ publicKey: 'pubkey', name: 'test key 1' }])
|
||||
delayedResolve([{ publicKey: 'pubkey', name: 'test key 1' }], delay)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -351,18 +373,6 @@ describe('VegaConnectDialog', () => {
|
||||
expect(screen.getByText('An unknown error occurred')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
function delayedResolve<T>(result: T): Promise<T> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(result), delay);
|
||||
});
|
||||
}
|
||||
|
||||
function delayedReject<T>(result: T): Promise<T> {
|
||||
return new Promise((_, reject) => {
|
||||
setTimeout(() => reject(result), delay);
|
||||
});
|
||||
}
|
||||
|
||||
async function selectJsonRpc() {
|
||||
expect(await screen.findByRole('dialog')).toBeInTheDocument();
|
||||
fireEvent.click(await screen.findByTestId('connector-jsonRpc'));
|
||||
@@ -439,4 +449,109 @@ describe('VegaConnectDialog', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('InjectedConnector', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearBrowserWallet();
|
||||
});
|
||||
|
||||
it('connects', async () => {
|
||||
const delay = 100;
|
||||
const vegaWindow = {
|
||||
getChainId: jest.fn(() =>
|
||||
delayedResolve({ chainID: mockChainId }, delay)
|
||||
),
|
||||
connectWallet: jest.fn(() => delayedResolve(null, delay)),
|
||||
disconnectWallet: jest.fn(() => delayedResolve(undefined, delay)),
|
||||
listKeys: jest.fn(() =>
|
||||
delayedResolve(
|
||||
{
|
||||
keys: [{ name: 'test key', publicKey: '0x123' }],
|
||||
},
|
||||
100
|
||||
)
|
||||
),
|
||||
};
|
||||
mockBrowserWallet(vegaWindow);
|
||||
render(generateJSX());
|
||||
await selectInjected();
|
||||
|
||||
// Chain check
|
||||
expect(screen.getByText('Verifying chain')).toBeInTheDocument();
|
||||
expect(vegaWindow.getChainId).toHaveBeenCalled();
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(delay);
|
||||
});
|
||||
|
||||
// Await user connect
|
||||
expect(screen.getByText('Connecting...')).toBeInTheDocument();
|
||||
expect(vegaWindow.connectWallet).toHaveBeenCalled();
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(delay);
|
||||
});
|
||||
|
||||
// Connect (list keys)
|
||||
expect(vegaWindow.listKeys).toHaveBeenCalled();
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(delay);
|
||||
});
|
||||
expect(screen.getByText('Successfully connected')).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(CLOSE_DELAY);
|
||||
});
|
||||
expect(mockCloseVegaDialog).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('handles invalid chain', async () => {
|
||||
const delay = 100;
|
||||
const invalidChain = 'invalid chain';
|
||||
const vegaWindow = {
|
||||
getChainId: jest.fn(() =>
|
||||
delayedResolve({ chainID: invalidChain }, delay)
|
||||
),
|
||||
connectWallet: jest.fn(() => delayedResolve(null, delay)),
|
||||
disconnectWallet: jest.fn(() => delayedResolve(undefined, delay)),
|
||||
listKeys: jest.fn(() =>
|
||||
delayedResolve(
|
||||
{
|
||||
keys: [{ name: 'test key', publicKey: '0x123' }],
|
||||
},
|
||||
100
|
||||
)
|
||||
),
|
||||
};
|
||||
mockBrowserWallet(vegaWindow);
|
||||
render(generateJSX());
|
||||
await selectInjected();
|
||||
|
||||
// Chain check
|
||||
expect(screen.getByText('Verifying chain')).toBeInTheDocument();
|
||||
expect(vegaWindow.getChainId).toHaveBeenCalled();
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(delay);
|
||||
});
|
||||
|
||||
expect(screen.getByText('Wrong network')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
new RegExp(`set your wallet network in your app to "${mockChainId}"`)
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
async function selectInjected() {
|
||||
expect(await screen.findByRole('dialog')).toBeInTheDocument();
|
||||
fireEvent.click(await screen.findByTestId('connector-injected'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,45 +3,50 @@ import {
|
||||
Button,
|
||||
Dialog,
|
||||
FormGroup,
|
||||
Icon,
|
||||
Input,
|
||||
Link,
|
||||
Loader,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { WalletClientError } from '@vegaprotocol/wallet-client';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { VegaConnector } from '../connectors';
|
||||
import { InjectedConnector } from '../connectors';
|
||||
import { ViewConnector } from '../connectors';
|
||||
import { JsonRpcConnector, RestConnector } from '../connectors';
|
||||
import { RestConnectorForm } from './rest-connector-form';
|
||||
import { JsonRpcConnectorForm } from './json-rpc-connector-form';
|
||||
import {
|
||||
Networks,
|
||||
useEnvironment,
|
||||
ExternalLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
ConnectDialogContent,
|
||||
ConnectDialogFooter,
|
||||
ConnectDialogTitle,
|
||||
} from './connect-dialog-elements';
|
||||
import type { Status } from '../use-json-rpc-connect';
|
||||
import type { Status as JsonRpcStatus } from '../use-json-rpc-connect';
|
||||
import type { Status as InjectedStatus } from '../use-injected-connector';
|
||||
import { useJsonRpcConnect } from '../use-json-rpc-connect';
|
||||
import { ViewConnectorForm } from './view-connector-form';
|
||||
import { useChainIdQuery } from './__generated__/ChainId';
|
||||
import { useVegaWallet } from '../use-vega-wallet';
|
||||
import { useInjectedConnector } from '../use-injected-connector';
|
||||
import { InjectedConnectorForm } from './injected-connector-form';
|
||||
|
||||
export const CLOSE_DELAY = 1700;
|
||||
type Connectors = { [key: string]: VegaConnector };
|
||||
type WalletType = 'jsonRpc' | 'hosted' | 'view';
|
||||
export type WalletType = 'injected' | 'jsonRpc' | 'rest' | 'view';
|
||||
|
||||
export interface VegaConnectDialogProps {
|
||||
connectors: Connectors;
|
||||
onChangeOpen?: (open: boolean) => void;
|
||||
riskMessage?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface VegaWalletDialogStore {
|
||||
vegaWalletDialogOpen: boolean;
|
||||
updateVegaWalletDialog: (open: boolean) => void;
|
||||
openVegaWalletDialog: () => void;
|
||||
closeVegaWalletDialog: () => void;
|
||||
}
|
||||
|
||||
export const useVegaWalletDialogStore = create<VegaWalletDialogStore>()(
|
||||
(set) => ({
|
||||
vegaWalletDialogOpen: false,
|
||||
@@ -52,32 +57,20 @@ export const useVegaWalletDialogStore = create<VegaWalletDialogStore>()(
|
||||
})
|
||||
);
|
||||
|
||||
export interface VegaWalletDialogStore {
|
||||
vegaWalletDialogOpen: boolean;
|
||||
updateVegaWalletDialog: (open: boolean) => void;
|
||||
openVegaWalletDialog: () => void;
|
||||
closeVegaWalletDialog: () => void;
|
||||
}
|
||||
|
||||
export const VegaConnectDialog = ({
|
||||
connectors,
|
||||
onChangeOpen,
|
||||
riskMessage,
|
||||
}: VegaConnectDialogProps) => {
|
||||
const { disconnect, acknowledgeNeeded } = useVegaWallet();
|
||||
const vegaWalletDialogOpen = useVegaWalletDialogStore(
|
||||
(store) => store.vegaWalletDialogOpen
|
||||
);
|
||||
const updateVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => (open: boolean) => {
|
||||
store.updateVegaWalletDialog(open);
|
||||
onChangeOpen?.(open);
|
||||
}
|
||||
);
|
||||
const closeVegaWalletDialog = useVegaWalletDialogStore((store) => () => {
|
||||
store.closeVegaWalletDialog();
|
||||
onChangeOpen?.(false);
|
||||
});
|
||||
const { disconnect, acknowledgeNeeded } = useVegaWallet();
|
||||
|
||||
const onVegaWalletDialogChange = useCallback(
|
||||
(open: boolean) => {
|
||||
updateVegaWalletDialog(open);
|
||||
@@ -88,41 +81,9 @@ export const VegaConnectDialog = ({
|
||||
[updateVegaWalletDialog, acknowledgeNeeded, disconnect]
|
||||
);
|
||||
|
||||
const { data, error, loading } = useChainIdQuery();
|
||||
|
||||
const renderContent = () => {
|
||||
if (error) {
|
||||
return (
|
||||
<ConnectDialogContent>
|
||||
<ConnectDialogTitle>
|
||||
{t('Could not retrieve chain id')}
|
||||
</ConnectDialogTitle>
|
||||
<ConnectDialogFooter />
|
||||
</ConnectDialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<ConnectDialogContent>
|
||||
<ConnectDialogTitle>{t('Fetching chain ID')}</ConnectDialogTitle>
|
||||
<div className="flex justify-center items-center my-6">
|
||||
<Loader />
|
||||
</div>
|
||||
<ConnectDialogFooter />
|
||||
</ConnectDialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ConnectDialogContainer
|
||||
connectors={connectors}
|
||||
closeDialog={closeVegaWalletDialog}
|
||||
appChainId={data.statistics.chainId}
|
||||
riskMessage={riskMessage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
// Ensure we have a chain Id so we can compare with wallet chain id.
|
||||
// This value will already be in the cache, if it failed the app wont render
|
||||
const { data } = useChainIdQuery();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -130,29 +91,35 @@ export const VegaConnectDialog = ({
|
||||
size="small"
|
||||
onChange={onVegaWalletDialogChange}
|
||||
>
|
||||
{renderContent()}
|
||||
{data && (
|
||||
<ConnectDialogContainer
|
||||
connectors={connectors}
|
||||
appChainId={data.statistics.chainId}
|
||||
riskMessage={riskMessage}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const ConnectDialogContainer = ({
|
||||
connectors,
|
||||
closeDialog,
|
||||
appChainId,
|
||||
riskMessage,
|
||||
}: {
|
||||
connectors: Connectors;
|
||||
closeDialog: () => void;
|
||||
appChainId: string;
|
||||
riskMessage?: React.ReactNode;
|
||||
}) => {
|
||||
const { VEGA_WALLET_URL, VEGA_ENV, HOSTED_WALLET_URL } = useEnvironment();
|
||||
const closeDialog = useVegaWalletDialogStore(
|
||||
(store) => store.closeVegaWalletDialog
|
||||
);
|
||||
const [selectedConnector, setSelectedConnector] = useState<VegaConnector>();
|
||||
const [walletUrl, setWalletUrl] = useState(VEGA_WALLET_URL || '');
|
||||
const [walletType, setWalletType] = useState<WalletType>();
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setSelectedConnector(undefined);
|
||||
setWalletType(undefined);
|
||||
}, []);
|
||||
|
||||
const delayedOnConnect = useCallback(() => {
|
||||
@@ -161,52 +128,59 @@ const ConnectDialogContainer = ({
|
||||
}, CLOSE_DELAY);
|
||||
}, [closeDialog]);
|
||||
|
||||
const { connect, ...jsonRpcState } = useJsonRpcConnect(delayedOnConnect);
|
||||
const { connect: jsonRpcConnect, ...jsonRpcState } =
|
||||
useJsonRpcConnect(delayedOnConnect);
|
||||
const { connect: injectedConnect, ...injectedState } =
|
||||
useInjectedConnector(delayedOnConnect);
|
||||
|
||||
const handleSelect = (type: WalletType, isHosted = false) => {
|
||||
let connector;
|
||||
const handleSelect = (type: WalletType) => {
|
||||
const connector = connectors[type];
|
||||
|
||||
if (isHosted) {
|
||||
// If the user has selected hosted wallet ensure that we are connecting to https://vega-hosted-wallet.on.fleek.co/
|
||||
// otherwise use the default walletUrl or what has been put in the input
|
||||
connector = connectors['rest'];
|
||||
connector.url = HOSTED_WALLET_URL || walletUrl;
|
||||
} else {
|
||||
connector = connectors[type];
|
||||
connector.url = walletUrl;
|
||||
}
|
||||
// If type is rest user has selected the hosted wallet option. So here
|
||||
// we ensure that we are connecting to https://vega-hosted-wallet.on.fleek.co/
|
||||
// otherwise use walletUrl which defaults to the localhost:1789
|
||||
connector.url = type === 'rest' ? HOSTED_WALLET_URL : walletUrl;
|
||||
|
||||
if (!connector) {
|
||||
// we should never get here unless connectors are not configured correctly
|
||||
throw new Error(`Connector type: ${type} not configured`);
|
||||
}
|
||||
|
||||
setSelectedConnector(connector);
|
||||
setWalletType(type);
|
||||
|
||||
// Immediately connect on selection if jsonRpc is selected, we can't do this
|
||||
// for rest because we need to show an authentication form
|
||||
if (connector instanceof JsonRpcConnector) {
|
||||
connect(connector, appChainId);
|
||||
jsonRpcConnect(connector, appChainId);
|
||||
} else if (connector instanceof InjectedConnector) {
|
||||
injectedConnect(connector, appChainId);
|
||||
}
|
||||
};
|
||||
|
||||
return selectedConnector !== undefined && walletType !== undefined ? (
|
||||
<SelectedForm
|
||||
type={walletType}
|
||||
connector={selectedConnector}
|
||||
jsonRpcState={jsonRpcState}
|
||||
onConnect={closeDialog}
|
||||
appChainId={appChainId}
|
||||
reset={reset}
|
||||
riskMessage={riskMessage}
|
||||
/>
|
||||
) : (
|
||||
<ConnectorList
|
||||
walletUrl={walletUrl}
|
||||
setWalletUrl={setWalletUrl}
|
||||
onSelect={handleSelect}
|
||||
isMainnet={VEGA_ENV === Networks.MAINNET}
|
||||
/>
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogContent>
|
||||
{selectedConnector !== undefined ? (
|
||||
<SelectedForm
|
||||
connector={selectedConnector}
|
||||
jsonRpcState={jsonRpcState}
|
||||
injectedState={injectedState}
|
||||
onConnect={closeDialog}
|
||||
appChainId={appChainId}
|
||||
reset={reset}
|
||||
riskMessage={riskMessage}
|
||||
/>
|
||||
) : (
|
||||
<ConnectorList
|
||||
walletUrl={walletUrl}
|
||||
setWalletUrl={setWalletUrl}
|
||||
onSelect={handleSelect}
|
||||
isMainnet={VEGA_ENV === Networks.MAINNET}
|
||||
/>
|
||||
)}
|
||||
</ConnectDialogContent>
|
||||
<ConnectDialogFooter connector={selectedConnector} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -216,136 +190,129 @@ const ConnectorList = ({
|
||||
setWalletUrl,
|
||||
isMainnet,
|
||||
}: {
|
||||
onSelect: (type: WalletType, isHosted?: boolean) => void;
|
||||
onSelect: (type: WalletType) => void;
|
||||
walletUrl: string;
|
||||
setWalletUrl: (value: string) => void;
|
||||
isMainnet: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogContent>
|
||||
<ConnectDialogTitle>{t('Connect')}</ConnectDialogTitle>
|
||||
<CustomUrlInput walletUrl={walletUrl} setWalletUrl={setWalletUrl} />
|
||||
<ul data-testid="connectors-list" className="mb-6">
|
||||
<ConnectDialogTitle>{t('Connect')}</ConnectDialogTitle>
|
||||
<CustomUrlInput walletUrl={walletUrl} setWalletUrl={setWalletUrl} />
|
||||
<ul data-testid="connectors-list" className="mb-6">
|
||||
<li className="mb-4 last:mb-0">
|
||||
<ConnectionOption
|
||||
type="jsonRpc"
|
||||
text={t('Connect Vega wallet')}
|
||||
onClick={() => onSelect('jsonRpc')}
|
||||
/>
|
||||
</li>
|
||||
{'vega' in window && (
|
||||
<li className="mb-4 last:mb-0">
|
||||
<ConnectionOption
|
||||
type="jsonRpc"
|
||||
text={t('Connect Vega wallet')}
|
||||
onClick={() => onSelect('jsonRpc')}
|
||||
type="injected"
|
||||
text={t('Connect Web wallet')}
|
||||
onClick={() => onSelect('injected')}
|
||||
/>
|
||||
</li>
|
||||
{!isMainnet && (
|
||||
<li className="mb-4 last:mb-0">
|
||||
<ConnectionOption
|
||||
type="hosted"
|
||||
text={t('Hosted Fairground wallet')}
|
||||
onClick={() => onSelect('hosted', true)}
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
)}
|
||||
{!isMainnet && (
|
||||
<li className="mb-4 last:mb-0">
|
||||
<div className="my-4 text-center text-vega-dark-400">{t('OR')}</div>
|
||||
<ConnectionOption
|
||||
type="view"
|
||||
text={t('View as vega user')}
|
||||
onClick={() => onSelect('view')}
|
||||
type="rest"
|
||||
text={t('Hosted Fairground wallet')}
|
||||
onClick={() => onSelect('rest')}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</ConnectDialogContent>
|
||||
<ConnectDialogFooter />
|
||||
)}
|
||||
<li className="mb-4 last:mb-0">
|
||||
<div className="my-4 text-center">{t('OR')}</div>
|
||||
<ConnectionOption
|
||||
type="view"
|
||||
text={t('View as vega user')}
|
||||
onClick={() => onSelect('view')}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectedForm = ({
|
||||
type,
|
||||
connector,
|
||||
appChainId,
|
||||
jsonRpcState,
|
||||
injectedState,
|
||||
reset,
|
||||
onConnect,
|
||||
riskMessage,
|
||||
}: {
|
||||
type: WalletType;
|
||||
connector: VegaConnector;
|
||||
appChainId: string;
|
||||
jsonRpcState: {
|
||||
status: Status;
|
||||
status: JsonRpcStatus;
|
||||
error: WalletClientError | null;
|
||||
};
|
||||
injectedState: {
|
||||
status: InjectedStatus;
|
||||
error: Error | null;
|
||||
};
|
||||
reset: () => void;
|
||||
onConnect: () => void;
|
||||
riskMessage?: React.ReactNode;
|
||||
}) => {
|
||||
if (connector instanceof InjectedConnector) {
|
||||
return (
|
||||
<InjectedConnectorForm
|
||||
status={injectedState.status}
|
||||
error={injectedState.error}
|
||||
onConnect={onConnect}
|
||||
appChainId={appChainId}
|
||||
reset={reset}
|
||||
riskMessage={riskMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (connector instanceof RestConnector) {
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogContent>
|
||||
<button
|
||||
onClick={reset}
|
||||
className="absolute p-2 top-0 left-0 md:top-2 md:left-2"
|
||||
data-testid="back-button"
|
||||
>
|
||||
<Icon name={'chevron-left'} ariaLabel="back" size={4} />
|
||||
</button>
|
||||
<ConnectDialogTitle>{t('Connect')}</ConnectDialogTitle>
|
||||
<div className="mb-2">
|
||||
<RestConnectorForm connector={connector} onConnect={onConnect} />
|
||||
</div>
|
||||
</ConnectDialogContent>
|
||||
{type === 'hosted' ? (
|
||||
<ConnectDialogFooter>
|
||||
<p className="text-center">
|
||||
{t('For demo purposes get a ')}
|
||||
<Link
|
||||
href={ExternalLinks.VEGA_WALLET_HOSTED_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t('hosted wallet')}
|
||||
</Link>
|
||||
{t(', or for the real experience create a wallet in the ')}
|
||||
<Link href={ExternalLinks.VEGA_WALLET_URL}>
|
||||
{t('Vega wallet app')}
|
||||
</Link>
|
||||
</p>
|
||||
</ConnectDialogFooter>
|
||||
) : (
|
||||
<ConnectDialogFooter />
|
||||
)}
|
||||
<button
|
||||
onClick={reset}
|
||||
className="absolute p-2 top-0 left-0 md:top-2 md:left-2"
|
||||
data-testid="back-button"
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_LEFT} />
|
||||
</button>
|
||||
<ConnectDialogTitle>{t('Connect')}</ConnectDialogTitle>
|
||||
<div className="mb-2">
|
||||
<RestConnectorForm connector={connector} onConnect={onConnect} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (connector instanceof JsonRpcConnector) {
|
||||
return (
|
||||
<ConnectDialogContent>
|
||||
<JsonRpcConnectorForm
|
||||
connector={connector}
|
||||
status={jsonRpcState.status}
|
||||
error={jsonRpcState.error}
|
||||
onConnect={onConnect}
|
||||
appChainId={appChainId}
|
||||
reset={reset}
|
||||
riskMessage={riskMessage}
|
||||
/>
|
||||
</ConnectDialogContent>
|
||||
<JsonRpcConnectorForm
|
||||
connector={connector}
|
||||
status={jsonRpcState.status}
|
||||
error={jsonRpcState.error}
|
||||
onConnect={onConnect}
|
||||
appChainId={appChainId}
|
||||
reset={reset}
|
||||
riskMessage={riskMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (connector instanceof ViewConnector) {
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogContent>
|
||||
<ViewConnectorForm
|
||||
connector={connector}
|
||||
onConnect={onConnect}
|
||||
reset={reset}
|
||||
/>
|
||||
</ConnectDialogContent>
|
||||
<ConnectDialogFooter />
|
||||
</>
|
||||
<ViewConnectorForm
|
||||
connector={connector}
|
||||
onConnect={onConnect}
|
||||
reset={reset}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -366,12 +333,12 @@ const ConnectionOption = ({
|
||||
onClick={onClick}
|
||||
size="lg"
|
||||
fill={true}
|
||||
variant={['hosted', 'view'].includes(type) ? 'default' : 'primary'}
|
||||
variant={['rest', 'view'].includes(type) ? 'default' : 'primary'}
|
||||
data-testid={`connector-${type}`}
|
||||
>
|
||||
<span className="-mx-6 flex text-left justify-between items-center">
|
||||
<span className="-mx-10 flex text-left justify-between items-center">
|
||||
{text}
|
||||
<Icon name="chevron-right" />
|
||||
<VegaIcon name={VegaIconNames.ARROW_RIGHT} />
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
@@ -387,9 +354,7 @@ const CustomUrlInput = ({
|
||||
const [urlInputExpanded, setUrlInputExpanded] = useState(false);
|
||||
return urlInputExpanded ? (
|
||||
<>
|
||||
<p className="mb-2 text-neutral-600 dark:text-neutral-400">
|
||||
{t('Custom wallet location')}
|
||||
</p>
|
||||
<p className="mb-2">{t('Custom wallet location')}</p>
|
||||
<FormGroup
|
||||
labelFor="wallet-url"
|
||||
label={t('Custom wallet location')}
|
||||
@@ -401,12 +366,10 @@ const CustomUrlInput = ({
|
||||
name="wallet-url"
|
||||
/>
|
||||
</FormGroup>
|
||||
<p className="mb-2 text-neutral-600 dark:text-neutral-400">
|
||||
{t('Choose wallet app to connect')}
|
||||
</p>
|
||||
<p className="mb-2">{t('Choose wallet app to connect')}</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="mb-6 text-neutral-600 dark:text-neutral-400">
|
||||
<p className="mb-6">
|
||||
{t(
|
||||
'Choose wallet app to connect, or to change port or server URL enter a '
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Status } from '../use-injected-connector';
|
||||
import { ConnectDialogTitle } from './connect-dialog-elements';
|
||||
import type { ReactNode } from 'react';
|
||||
import {
|
||||
Button,
|
||||
ButtonLink,
|
||||
Diamond,
|
||||
Loader,
|
||||
Tick,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { setAcknowledged } from '../storage';
|
||||
import { useVegaWallet } from '../use-vega-wallet';
|
||||
|
||||
export const InjectedConnectorForm = ({
|
||||
status,
|
||||
onConnect,
|
||||
riskMessage,
|
||||
appChainId,
|
||||
reset,
|
||||
error,
|
||||
}: {
|
||||
// connector: JsonRpcConnector;
|
||||
appChainId: string;
|
||||
status: Status;
|
||||
error: Error | null;
|
||||
onConnect: () => void;
|
||||
reset: () => void;
|
||||
riskMessage?: React.ReactNode;
|
||||
}) => {
|
||||
const { disconnect } = useVegaWallet();
|
||||
|
||||
if (status === Status.Idle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (status === Status.Error) {
|
||||
return <Error error={error} appChainId={appChainId} onTryAgain={reset} />;
|
||||
}
|
||||
|
||||
if (status === Status.GettingChainId) {
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogTitle>{t('Verifying chain')}</ConnectDialogTitle>
|
||||
<Center>
|
||||
<Loader />
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === Status.Connected) {
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogTitle>{t('Successfully connected')}</ConnectDialogTitle>
|
||||
<Center>
|
||||
<Tick />
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === Status.Connecting) {
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogTitle>{t('Connecting...')}</ConnectDialogTitle>
|
||||
<Center>
|
||||
<Diamond />
|
||||
</Center>
|
||||
<p className="text-center">
|
||||
{t(
|
||||
"Approve the connection from your Vega wallet app. If you have multiple wallets you'll need to choose which to connect with."
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === Status.AcknowledgeNeeded) {
|
||||
const setConnection = () => {
|
||||
setAcknowledged();
|
||||
onConnect();
|
||||
};
|
||||
const handleDisagree = () => {
|
||||
disconnect();
|
||||
onConnect(); // this is dialog closing
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogTitle>{t('Understand the risk')}</ConnectDialogTitle>
|
||||
{riskMessage}
|
||||
<div className="grid grid-cols-2 gap-5">
|
||||
<div>
|
||||
<Button onClick={handleDisagree} fill>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button onClick={setConnection} variant="primary" fill>
|
||||
{t('I agree')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const Center = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<div className="flex justify-center items-center my-6">{children}</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Error = ({
|
||||
error,
|
||||
appChainId,
|
||||
onTryAgain,
|
||||
}: {
|
||||
error: Error | null;
|
||||
appChainId: string;
|
||||
onTryAgain: () => void;
|
||||
}) => {
|
||||
let title = t('Something went wrong');
|
||||
let text: ReactNode | undefined = t('An unknown error occurred');
|
||||
const tryAgain: ReactNode | null = (
|
||||
<p className="text-center">
|
||||
<ButtonLink onClick={onTryAgain}>{t('Try again')}</ButtonLink>
|
||||
</p>
|
||||
);
|
||||
|
||||
if (error) {
|
||||
if (error.message === 'Invalid chain') {
|
||||
title = t('Wrong network');
|
||||
text = t(
|
||||
'To complete your wallet connection, set your wallet network in your app to "%s".',
|
||||
appChainId
|
||||
);
|
||||
} else if (error.message === 'window.vega not found') {
|
||||
title = t('No wallet detected');
|
||||
text = t('Vega browser extension not installed');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogTitle>{title}</ConnectDialogTitle>
|
||||
<p className="text-center mb-2 first-letter:uppercase">{text}</p>
|
||||
{tryAgain}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,21 +1,83 @@
|
||||
import type { VegaConnector } from './vega-connector';
|
||||
import { clearConfig, setConfig } from '../storage';
|
||||
import type { Transaction, VegaConnector } from './vega-connector';
|
||||
|
||||
declare global {
|
||||
interface Vega {
|
||||
getChainId: () => Promise<{ chainID: string }>;
|
||||
connectWallet: () => Promise<null>;
|
||||
disconnectWallet: () => Promise<void>;
|
||||
listKeys: () => Promise<{
|
||||
keys: Array<{ name: string; publicKey: string }>;
|
||||
}>;
|
||||
sendTransaction: (params: {
|
||||
publicKey: string;
|
||||
transaction: Transaction;
|
||||
sendingMode: 'TYPE_SYNC';
|
||||
}) => Promise<{
|
||||
receivedAt: string;
|
||||
sentAt: string;
|
||||
transaction: {
|
||||
from: {
|
||||
pubKey: string;
|
||||
};
|
||||
inputData: string;
|
||||
pow: {
|
||||
tid: string;
|
||||
nonce: string;
|
||||
};
|
||||
signature: {
|
||||
algo: string;
|
||||
value: string;
|
||||
version: number;
|
||||
};
|
||||
version: number;
|
||||
};
|
||||
transactionHash: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
vega: Vega;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy injected connector that we may use when browser wallet is implemented
|
||||
*/
|
||||
export class InjectedConnector implements VegaConnector {
|
||||
description = 'Connects using the Vega wallet browser extension';
|
||||
|
||||
async getChainId() {
|
||||
return window.vega.getChainId();
|
||||
}
|
||||
|
||||
connectWallet() {
|
||||
return window.vega.connectWallet();
|
||||
}
|
||||
|
||||
async connect() {
|
||||
return [{ publicKey: '0x123', name: 'text key' }];
|
||||
const res = await window.vega.listKeys();
|
||||
setConfig({
|
||||
connector: 'injected',
|
||||
token: null, // no token required for injected
|
||||
url: null, // no url for injected
|
||||
});
|
||||
return res.keys;
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
return;
|
||||
disconnect() {
|
||||
clearConfig();
|
||||
return window.vega.disconnectWallet();
|
||||
}
|
||||
|
||||
// @ts-ignore injected connector is not implemented
|
||||
sendTx() {
|
||||
throw new Error('Not implemented');
|
||||
async sendTx(pubKey: string, transaction: Transaction) {
|
||||
const result = await window.vega.sendTransaction({
|
||||
publicKey: pubKey,
|
||||
transaction,
|
||||
sendingMode: 'TYPE_SYNC' as const,
|
||||
});
|
||||
return {
|
||||
transactionHash: result.transactionHash,
|
||||
receivedAt: result.receivedAt,
|
||||
sentAt: result.sentAt,
|
||||
signature: result.transaction.signature.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,10 @@ export interface OrderSubmission {
|
||||
expiresAt?: string;
|
||||
postOnly?: boolean;
|
||||
reduceOnly?: boolean;
|
||||
icebergOpts?: {
|
||||
peakSize: string;
|
||||
minimumVisibleSize: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OrderCancellation {
|
||||
@@ -411,7 +415,7 @@ export interface PubKey {
|
||||
}
|
||||
|
||||
export interface VegaConnector {
|
||||
url: string | null;
|
||||
url?: string | null;
|
||||
|
||||
/** Connect to wallet and return keys */
|
||||
connect(): Promise<PubKey[] | null>;
|
||||
|
||||
@@ -106,7 +106,6 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
|
||||
if (!connector.current) {
|
||||
throw new Error('No connector');
|
||||
}
|
||||
|
||||
return connector.current.sendTx(pubkey, transaction);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { LocalStorage } from '@vegaprotocol/utils';
|
||||
|
||||
interface ConnectorConfig {
|
||||
token: string | null;
|
||||
connector: 'rest' | 'jsonRpc' | 'view';
|
||||
connector: 'injected' | 'rest' | 'jsonRpc' | 'view';
|
||||
url: string | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
export function mockBrowserWallet(overrides?: Partial<Vega>) {
|
||||
const vega: Vega = {
|
||||
getChainId: jest.fn().mockReturnValue(Promise.resolve({ chainID: '1' })),
|
||||
connectWallet: jest.fn().mockReturnValue(Promise.resolve(null)),
|
||||
disconnectWallet: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
listKeys: jest
|
||||
.fn()
|
||||
.mockReturnValue({ keys: [{ name: 'test key', publicKey: '0x123' }] }),
|
||||
sendTransaction: jest.fn().mockReturnValue({
|
||||
code: 1,
|
||||
data: '',
|
||||
height: '1',
|
||||
log: '',
|
||||
success: true,
|
||||
txHash: '0x123',
|
||||
}),
|
||||
...overrides,
|
||||
};
|
||||
// @ts-ignore globalThis has no index signature
|
||||
globalThis.vega = vega;
|
||||
return vega;
|
||||
}
|
||||
|
||||
export function clearBrowserWallet() {
|
||||
// @ts-ignore no index signature on globalThis
|
||||
delete globalThis['vega'];
|
||||
}
|
||||
|
||||
export function delayedResolve<T>(result: T, delay = 0): Promise<T> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(result), delay);
|
||||
});
|
||||
}
|
||||
|
||||
export function delayedReject<T>(result: T, delay = 0): Promise<T> {
|
||||
return new Promise((_, reject) => {
|
||||
setTimeout(() => reject(result), delay);
|
||||
});
|
||||
}
|
||||
@@ -31,7 +31,14 @@ export function useEagerConnect(Connectors: {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await connect(Connectors[cfg.connector]);
|
||||
if (cfg.connector === 'injected') {
|
||||
const injectedInstance = Connectors[cfg.connector];
|
||||
// @ts-ignore only injected wallet has connectWallet method
|
||||
await injectedInstance.connectWallet();
|
||||
await connect(injectedInstance);
|
||||
} else {
|
||||
await connect(Connectors[cfg.connector]);
|
||||
}
|
||||
} catch {
|
||||
console.warn(`Failed to connect with connector: ${cfg.connector}`);
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { Status, useInjectedConnector } from './use-injected-connector';
|
||||
import type { ReactNode } from 'react';
|
||||
import { VegaWalletProvider } from './provider';
|
||||
import { InjectedConnector } from './connectors';
|
||||
import { mockBrowserWallet } from './test-helpers';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Networks } from '@vegaprotocol/environment';
|
||||
|
||||
jest.mock('@vegaprotocol/environment');
|
||||
|
||||
const setup = (callback = jest.fn()) => {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<VegaWalletProvider>{children}</VegaWalletProvider>
|
||||
);
|
||||
return renderHook(() => useInjectedConnector(callback), { wrapper });
|
||||
};
|
||||
|
||||
const injected = new InjectedConnector();
|
||||
|
||||
describe('useInjectedConnector', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore useEnvironment has been mocked
|
||||
useEnvironment.mockImplementation(() => ({ VEGA_ENV: Networks.TESTNET }));
|
||||
});
|
||||
it('attempts connection', async () => {
|
||||
const { result } = setup();
|
||||
expect(typeof result.current.connect).toBe('function');
|
||||
expect(result.current.status).toBe(Status.Idle);
|
||||
expect(result.current.error).toBe(null);
|
||||
});
|
||||
|
||||
it('errors if vega not injected', async () => {
|
||||
const { result } = setup();
|
||||
await act(async () => {
|
||||
result.current.connect(injected, '1');
|
||||
});
|
||||
expect(result.current.error?.message).toBe('window.vega not found');
|
||||
expect(result.current.status).toBe(Status.Error);
|
||||
});
|
||||
|
||||
it('errors if chain ids dont match', async () => {
|
||||
mockBrowserWallet();
|
||||
const { result } = setup();
|
||||
await act(async () => {
|
||||
result.current.connect(injected, '2'); // default mock chainId is '1'
|
||||
});
|
||||
expect(result.current.error?.message).toBe('Invalid chain');
|
||||
expect(result.current.status).toBe(Status.Error);
|
||||
});
|
||||
|
||||
it('errors if connection throws', async () => {
|
||||
const callback = jest.fn();
|
||||
mockBrowserWallet({
|
||||
getChainId: () => Promise.reject('failed'),
|
||||
});
|
||||
const { result } = setup(callback);
|
||||
|
||||
await act(async () => {
|
||||
result.current.connect(injected, '1'); // default mock chainId is '1'
|
||||
});
|
||||
expect(result.current.status).toBe(Status.Error);
|
||||
expect(result.current.error?.message).toBe('injected connection failed');
|
||||
});
|
||||
|
||||
it('connects', async () => {
|
||||
const callback = jest.fn();
|
||||
const vega = mockBrowserWallet();
|
||||
const { result } = setup(callback);
|
||||
|
||||
act(() => {
|
||||
result.current.connect(injected, '1'); // default mock chainId is '1'
|
||||
});
|
||||
expect(result.current.status).toBe(Status.GettingChainId);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(vega.connectWallet).toHaveBeenCalled();
|
||||
expect(vega.listKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(result.current.status).toBe(Status.Connected);
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('connects when aknowledgement required', async () => {
|
||||
const callback = jest.fn();
|
||||
// @ts-ignore useEnvironment has been mocked
|
||||
useEnvironment.mockImplementation(() => ({ VEGA_ENV: Networks.MAINNET }));
|
||||
|
||||
const vega = mockBrowserWallet();
|
||||
const { result } = setup(callback);
|
||||
|
||||
act(() => {
|
||||
result.current.connect(injected, '1'); // default mock chainId is '1'
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(vega.listKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(result.current.status).toBe(Status.AcknowledgeNeeded);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { InjectedConnector } from './connectors';
|
||||
import { useVegaWallet } from './use-vega-wallet';
|
||||
|
||||
export enum Status {
|
||||
Idle = 'Idle',
|
||||
GettingChainId = 'GettingChainId',
|
||||
Connecting = 'Connecting',
|
||||
Connected = 'Connected',
|
||||
Error = 'Error',
|
||||
AcknowledgeNeeded = 'AcknowledgeNeeded',
|
||||
}
|
||||
|
||||
export const useInjectedConnector = (onConnect: () => void) => {
|
||||
const { connect, acknowledgeNeeded } = useVegaWallet();
|
||||
const [status, setStatus] = useState(Status.Idle);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
const attemptConnect = useCallback(
|
||||
async (connector: InjectedConnector, appChainId: string) => {
|
||||
try {
|
||||
if (!('vega' in window)) {
|
||||
throw new Error('window.vega not found');
|
||||
}
|
||||
|
||||
setStatus(Status.GettingChainId);
|
||||
|
||||
const { chainID } = await connector.getChainId();
|
||||
|
||||
if (chainID !== appChainId) {
|
||||
throw new Error('Invalid chain');
|
||||
}
|
||||
|
||||
setStatus(Status.Connecting);
|
||||
await connector.connectWallet(); // authorize wallet
|
||||
await connect(connector); // connect with keys
|
||||
|
||||
if (acknowledgeNeeded) {
|
||||
setStatus(Status.AcknowledgeNeeded);
|
||||
} else {
|
||||
setStatus(Status.Connected);
|
||||
onConnect();
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
setError(err);
|
||||
} else {
|
||||
setError(new Error('injected connection failed'));
|
||||
}
|
||||
setStatus(Status.Error);
|
||||
}
|
||||
},
|
||||
[acknowledgeNeeded, connect, onConnect]
|
||||
);
|
||||
|
||||
return {
|
||||
status,
|
||||
error,
|
||||
connect: attemptConnect,
|
||||
};
|
||||
};
|
||||
@@ -11,7 +11,6 @@ export enum Status {
|
||||
GettingChainId = 'GettingChainId',
|
||||
Connecting = 'Connecting',
|
||||
GettingPerms = 'GettingPerms',
|
||||
ListingKeys = 'ListingKeys',
|
||||
Connected = 'Connected',
|
||||
Error = 'Error',
|
||||
AcknowledgeNeeded = 'AcknowledgeNeeded',
|
||||
|
||||
Reference in New Issue
Block a user