Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4a2e7d604 | ||
|
|
6c7d4808e1 | ||
|
|
3c931e180e | ||
|
|
d760e4cc8e | ||
|
|
6f59083169 | ||
|
|
f0b953fb71 | ||
|
|
d46af0009a | ||
|
|
cda3a5050e | ||
|
|
76048fea66 | ||
|
|
5bbb592b01 | ||
|
|
722897fdc6 | ||
|
|
961c6e2ee6 | ||
|
|
9e413e66ae | ||
|
|
c46d701361 | ||
|
|
0d39c2354c | ||
|
|
ad6f0c5798 | ||
|
|
38d13085fb | ||
|
|
c0f4278b81 | ||
|
|
b2777043b4 | ||
|
|
4d19b55096 | ||
|
|
7ea7362a7d | ||
|
|
bbfe42ddb1 | ||
|
|
b163af3e8a | ||
|
|
e6b3ff456d | ||
|
|
00dbb7dd60 | ||
|
|
82df401611 |
@@ -1,36 +0,0 @@
|
||||
name: Cypress Console tests -- live environment
|
||||
|
||||
# This workflow runs using provided url
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
url:
|
||||
description: 'Url'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
cypress-run:
|
||||
name: Run Cypress Trading tests -- live environment
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 20
|
||||
id: Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Run Cypress tests
|
||||
uses: cypress-io/github-action@v4
|
||||
with:
|
||||
browser: chrome
|
||||
record: true
|
||||
project: ./apps/trading-e2e
|
||||
config: baseUrl=${{ github.event.inputs.url }}
|
||||
env: grepTags=@live
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -12,7 +12,6 @@ on:
|
||||
options:
|
||||
- explorer-e2e
|
||||
- governance-e2e
|
||||
- trading-e2e
|
||||
tags:
|
||||
description: 'Test tags to run'
|
||||
required: true
|
||||
|
||||
@@ -10,5 +10,5 @@ jobs:
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: '["explorer-e2e","governance-e2e","trading-e2e"]'
|
||||
projects: '["explorer-e2e","governance-e2e"]'
|
||||
tags: '@smoke @regression @slow'
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
navigateTo,
|
||||
navigation,
|
||||
turnTelemetryOff,
|
||||
setRiskAccepted,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
clickOnValidatorFromList,
|
||||
@@ -57,6 +58,7 @@ context(
|
||||
// 1002-STKE-002, 1002-STKE-032
|
||||
before('visit staking tab and connect vega wallet', function () {
|
||||
cy.visit('/');
|
||||
setRiskAccepted();
|
||||
ethereumWalletConnect();
|
||||
cy.connectVegaWallet();
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
navigateTo,
|
||||
navigation,
|
||||
turnTelemetryOff,
|
||||
setRiskAccepted,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
stakingPageAssociateTokens,
|
||||
@@ -57,6 +58,7 @@ context(
|
||||
function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
setRiskAccepted();
|
||||
cy.mockChainId();
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
|
||||
@@ -79,23 +79,23 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have information on active nodes', function () {
|
||||
it.skip('should have information on active nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.first()
|
||||
.should('contain.text', '1')
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'active nodes');
|
||||
});
|
||||
|
||||
it('should have information on consensus nodes', function () {
|
||||
it.skip('should have information on consensus nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.last()
|
||||
.should('contain.text', '1')
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'consensus nodes');
|
||||
});
|
||||
|
||||
it('should contain link to specific validators', function () {
|
||||
cy.getByTestId('validators')
|
||||
.should('have.length', '1')
|
||||
.should('have.length', '2')
|
||||
.each(($validator) => {
|
||||
cy.wrap($validator).find('a').should('have.attr', 'href');
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
navigation,
|
||||
setRiskAccepted,
|
||||
verifyPageHeader,
|
||||
verifyTabHighlighted,
|
||||
} from '../../support/common.functions';
|
||||
@@ -187,6 +188,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
before('connect wallets and click on validator', function () {
|
||||
cy.mockChainId();
|
||||
cy.visit('/validators');
|
||||
setRiskAccepted();
|
||||
cy.connectVegaWallet();
|
||||
clickOnValidatorFromList(0);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import { waitForSpinner } from '../../support/common.functions';
|
||||
import {
|
||||
setRiskAccepted,
|
||||
waitForSpinner,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
vegaWalletFaucetAssetsWithoutCheck,
|
||||
vegaWalletTeardown,
|
||||
@@ -11,7 +14,6 @@ const connectButton = 'connect-vega-wallet';
|
||||
const getVegaLink = 'link';
|
||||
const dialog = '[role="dialog"]:visible';
|
||||
const dialogHeader = 'dialog-title';
|
||||
const walletDialogHeader = 'wallet-dialog-title';
|
||||
const connectorsList = 'connectors-list';
|
||||
const dialogCloseBtn = 'dialog-close';
|
||||
const accountNo = 'vega-account-truncated';
|
||||
@@ -34,6 +36,7 @@ context(
|
||||
() => {
|
||||
before('visit token home page', () => {
|
||||
cy.visit('/');
|
||||
setRiskAccepted();
|
||||
cy.get(walletContainer, { timeout: 60000 }).should('be.visible');
|
||||
});
|
||||
|
||||
@@ -63,17 +66,12 @@ context(
|
||||
|
||||
it('should have Connect Vega header visible', () => {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.getByTestId(walletDialogHeader)
|
||||
cy.getByTestId(connectorsList)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Get a Vega wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have jsonRpc and hosted connection options visible on list', function () {
|
||||
cy.getByTestId(connectorsList).within(() => {
|
||||
cy.getByTestId('connector-jsonRpc')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Use the Desktop App/CLI');
|
||||
.and(
|
||||
'have.text',
|
||||
'Get the Vega WalletGet MetaMask>_Command Line WalletView as public key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +86,6 @@ context(
|
||||
before('connect vega wallet', function () {
|
||||
cy.mockChainId();
|
||||
cy.visit('/');
|
||||
cy.wait('@ChainId');
|
||||
cy.connectVegaWallet();
|
||||
vegaWalletTeardown();
|
||||
});
|
||||
|
||||
@@ -102,6 +102,12 @@ export function turnTelemetryOff() {
|
||||
);
|
||||
}
|
||||
|
||||
export function setRiskAccepted() {
|
||||
cy.window().then((win) =>
|
||||
win.localStorage.setItem('vega_wallet_risk_accepted', 'true')
|
||||
);
|
||||
}
|
||||
|
||||
export function dissociateFromSecondWalletKey() {
|
||||
const secondWalletKey = Cypress.env('vegaWalletPublicKey2Short');
|
||||
cy.getByTestId('vega-in-wallet')
|
||||
|
||||
@@ -26,9 +26,9 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
const { token, staking, vesting } = useContracts();
|
||||
const setAssociatedBalances = useRefreshAssociatedBalances();
|
||||
const [balancesLoaded, setBalancesLoaded] = React.useState(false);
|
||||
const vegaConnecting = useEagerConnect();
|
||||
const vegaWalletStatus = useEagerConnect();
|
||||
|
||||
const loaded = balancesLoaded && !vegaConnecting;
|
||||
const loaded = balancesLoaded && vegaWalletStatus !== 'connecting';
|
||||
|
||||
React.useEffect(() => {
|
||||
const run = async () => {
|
||||
@@ -169,3 +169,5 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
}
|
||||
return <Suspense fallback={loading}>{children}</Suspense>;
|
||||
};
|
||||
|
||||
AppLoader.displayName = 'AppLoader';
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
useLinks,
|
||||
DApp,
|
||||
CONSOLE_REWARDS_PAGE,
|
||||
} from '@vegaprotocol/environment';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
NotificationBanner,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { useMatch } from 'react-router-dom';
|
||||
import Routes from '../../routes/routes';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
const ConsoleRewardsLink = ({ children }: { children: ReactNode }) => {
|
||||
const consoleLink = useLinks(DApp.Console);
|
||||
return (
|
||||
<ExternalLink
|
||||
href={consoleLink(CONSOLE_REWARDS_PAGE)}
|
||||
className="underline inline-flex gap-1 items-center"
|
||||
title="Rewards in Console"
|
||||
>
|
||||
<span>{children}</span>
|
||||
<VegaIcon size={12} name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</ExternalLink>
|
||||
);
|
||||
};
|
||||
|
||||
export const RewardsMovedNotification = () => {
|
||||
const onRewardsPage = useMatch(Routes.REWARDS);
|
||||
if (!onRewardsPage) return null;
|
||||
|
||||
return (
|
||||
<NotificationBanner intent={Intent.Warning}>
|
||||
<Trans
|
||||
i18nKey="rewardsMovedNotification"
|
||||
components={[<ConsoleRewardsLink>Console</ConsoleRewardsLink>]}
|
||||
/>
|
||||
</NotificationBanner>
|
||||
);
|
||||
};
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
ProtocolUpgradeProposalNotification,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
|
||||
import { RewardsMovedNotification } from '../notifications/rewards-moved-notification';
|
||||
|
||||
interface AppLayoutProps {
|
||||
children: ReactNode;
|
||||
@@ -45,8 +46,10 @@ export const AppLayout = ({ children }: AppLayoutProps) => {
|
||||
|
||||
const NotificationsContainer = () => {
|
||||
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
|
||||
|
||||
return (
|
||||
<div data-testid="banners">
|
||||
<RewardsMovedNotification />
|
||||
<ProtocolUpgradeProposalNotification
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
|
||||
@@ -111,3 +111,4 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
</ContractsContext.Provider>
|
||||
);
|
||||
};
|
||||
ContractsProvider.displayName = 'ContractsProvider';
|
||||
|
||||
+1
-1
@@ -288,7 +288,7 @@ describe('Consensus validators table', () => {
|
||||
|
||||
expect(
|
||||
grid.querySelector('[role="gridcell"][col-id="totalPenalties"]')
|
||||
).toHaveTextContent('13.16%');
|
||||
).toHaveTextContent('10.07%');
|
||||
|
||||
expect(
|
||||
grid.querySelector('[role="gridcell"][col-id="normalisedVotingPower"]')
|
||||
|
||||
+13
-3
@@ -185,15 +185,19 @@ export const ConsensusValidatorsTable = ({
|
||||
const { rawValidatorScore: previousEpochValidatorScore } =
|
||||
getLastEpochScoreAndPerformance(previousEpochData, id);
|
||||
|
||||
const overstakingPenalty = calculateOverallPenalty(
|
||||
const overstakingPenalty = calculateOverstakedPenalty(
|
||||
id,
|
||||
allNodesInPreviousEpoch
|
||||
);
|
||||
const totalPenalty = calculateOverstakedPenalty(
|
||||
const totalPenalty = calculateOverallPenalty(
|
||||
id,
|
||||
allNodesInPreviousEpoch
|
||||
);
|
||||
|
||||
const lastEpochDataForNode = allNodesInPreviousEpoch.find(
|
||||
(node) => node.id === id
|
||||
);
|
||||
|
||||
return {
|
||||
id,
|
||||
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
|
||||
@@ -239,6 +243,12 @@ export const ConsensusValidatorsTable = ({
|
||||
: undefined,
|
||||
[ValidatorFields.MULTISIG_ERROR]:
|
||||
multisigStatus?.showMultisigStatusError,
|
||||
[ValidatorFields.MULTISIG_PENALTY]: formatNumberPercentage(
|
||||
new BigNumber(1)
|
||||
.minus(lastEpochDataForNode?.rewardScore?.multisigScore ?? 1)
|
||||
.times(100),
|
||||
2
|
||||
),
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -378,7 +388,6 @@ export const ConsensusValidatorsTable = ({
|
||||
headerTooltip: t('StakeDescription').toString(),
|
||||
cellRenderer: TotalStakeRenderer,
|
||||
width: 120,
|
||||
sort: 'desc',
|
||||
},
|
||||
{
|
||||
field: ValidatorFields.PENDING_STAKE,
|
||||
@@ -400,6 +409,7 @@ export const ConsensusValidatorsTable = ({
|
||||
headerTooltip: t('NormalisedVotingPowerDescription').toString(),
|
||||
cellRenderer: VotingPowerRenderer,
|
||||
width: 120,
|
||||
sort: 'desc',
|
||||
},
|
||||
{
|
||||
field: ValidatorFields.TOTAL_PENALTIES,
|
||||
|
||||
@@ -40,6 +40,7 @@ export enum ValidatorFields {
|
||||
PENDING_USER_STAKE = 'pendingUserStake',
|
||||
USER_STAKE_SHARE = 'userStakeShare',
|
||||
MULTISIG_ERROR = 'multisigError',
|
||||
MULTISIG_PENALTY = 'multisigPenalty',
|
||||
}
|
||||
|
||||
export const addUserDataToValidator = (
|
||||
@@ -327,7 +328,7 @@ interface TotalPenaltiesRendererProps {
|
||||
overstakedAmount: string;
|
||||
overstakingPenalty: string;
|
||||
totalPenalties: string;
|
||||
multisigError?: boolean;
|
||||
multisigPenalty: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -346,11 +347,9 @@ export const TotalPenaltiesRenderer = ({
|
||||
<div data-testid="overstaked-penalty-tooltip">
|
||||
{t('overstakedPenalty')}: {data.overstakingPenalty}
|
||||
</div>
|
||||
{data.multisigError && (
|
||||
<div data-testid="multisig-error-tooltip">
|
||||
{t('multisigPenalty')}: 100%
|
||||
</div>
|
||||
)}
|
||||
<div data-testid="multisig-error-tooltip">
|
||||
{t('multisigPenalty')}: {data.multisigPenalty}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -37,7 +37,6 @@ import {
|
||||
import type { ReactNode } from 'react';
|
||||
import type { StakingNodeFieldsFragment } from '../__generated__/Staking';
|
||||
import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch';
|
||||
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
|
||||
|
||||
const statuses = {
|
||||
[Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ]: 'status-ersatz',
|
||||
@@ -105,9 +104,10 @@ export const ValidatorTable = ({
|
||||
};
|
||||
}, [node, previousEpochData?.epoch.validatorsConnection?.edges]);
|
||||
|
||||
const multisigStatus = previousEpochData
|
||||
? getMultisigStatusInfo(previousEpochData)
|
||||
: undefined;
|
||||
const previousNodeData =
|
||||
previousEpochData?.epoch.validatorsConnection?.edges?.find(
|
||||
(e) => e?.node.id === node.id
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -293,21 +293,15 @@ export const ValidatorTable = ({
|
||||
data-testid="multisig-penalty"
|
||||
className="flex gap-2 items-baseline"
|
||||
>
|
||||
{multisigStatus?.zeroScoreNodes.find(
|
||||
(n) => n.id === node.id
|
||||
) ? (
|
||||
<Tooltip
|
||||
description={t('multisigPenaltyThisNodeIndicator')}
|
||||
>
|
||||
<span className="inline-block w-2 h-2 rounded-full bg-vega-red-500"></span>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Tooltip description={t('multisigPenaltyDescription')}>
|
||||
<span>
|
||||
{formatNumberPercentage(
|
||||
BigNumber(
|
||||
multisigStatus?.showMultisigStatusError ? 100 : 0
|
||||
),
|
||||
new BigNumber(1)
|
||||
.minus(
|
||||
previousNodeData?.node.rewardScore?.multisigScore ??
|
||||
1
|
||||
)
|
||||
.times(100),
|
||||
2
|
||||
)}
|
||||
</span>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
NX_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=''
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
|
||||
NX_VEGA_URL=http://localhost:3008/graphql
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_SENTRY_DSN=https://dummy@o999999.ingest.sentry.io/9999999
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
|
||||
|
||||
# Expose some env vars to cypress environment for market setup
|
||||
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||
CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
|
||||
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
||||
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
CYPRESS_CONSOLE_URL=https://console.fairground.wtf
|
||||
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
|
||||
CYPRESS_ORACLE_PUBKEY=6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61
|
||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
|
||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
|
||||
CYPRESS_VEGA_ENV=CUSTOM
|
||||
CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65
|
||||
CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535
|
||||
CYPRESS_VEGA_TOKEN_URL=https://governance.fairground.wtf
|
||||
CYPRESS_VEGA_URL=http://localhost:3008/graphql
|
||||
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN=
|
||||
|
||||
# Cosmic elevator flags (MUST be doubled with CYPRESS_ prefix)
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
CYPRESS_NX_SUCCESSOR_MARKETS=true
|
||||
@@ -1,33 +0,0 @@
|
||||
NX_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=''
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
|
||||
NX_VEGA_URL=http://localhost:3008/graphql
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
|
||||
|
||||
# Expose some env vars to cypress environment for market setup
|
||||
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||
CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
|
||||
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
||||
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
CYPRESS_CONSOLE_URL=https://console.fairground.wtf
|
||||
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
|
||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
|
||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
|
||||
CYPRESS_VEGA_ENV=CUSTOM
|
||||
CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65
|
||||
CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535
|
||||
CYPRESS_VEGA_TOKEN_URL=https://governance.fairground.wtf
|
||||
CYPRESS_VEGA_URL=http://localhost:3008/graphql
|
||||
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN=
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {
|
||||
"cypress/unsafe-to-chain-command": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/plugins/index.js"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"no-undef": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
const { defineConfig } = require('cypress');
|
||||
|
||||
module.exports = defineConfig({
|
||||
reporter: '../../node_modules/cypress-mochawesome-reporter',
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
require('cypress-mochawesome-reporter/plugin')(on);
|
||||
require('@cypress/grep/src/plugin')(config);
|
||||
return config;
|
||||
},
|
||||
baseUrl: 'http://localhost:4200',
|
||||
fileServerFolder: '.',
|
||||
fixturesFolder: false,
|
||||
specPattern: '**/*.cy.{js,jsx,ts,tsx}',
|
||||
supportFile: './src/support/index.js',
|
||||
video: false,
|
||||
videosFolder: '../../dist/cypress/apps/trading-e2e/videos',
|
||||
videoUploadOnPasses: false,
|
||||
screenshotsFolder: '../../dist/cypress/apps/trading-e2e/screenshots',
|
||||
chromeWebSecurity: false,
|
||||
projectId: 'et4snf',
|
||||
defaultCommandTimeout: 10000,
|
||||
viewportWidth: 1800,
|
||||
viewportHeight: 900,
|
||||
responseTimeout: 50000,
|
||||
requestTimeout: 20000,
|
||||
retries: 1,
|
||||
testIsolation: false,
|
||||
experimentalMemoryManagement: true,
|
||||
},
|
||||
env: {
|
||||
ETHERSCAN_URL: 'https://sepolia.etherscan.io',
|
||||
ETHEREUM_CHAIN_ID: 11155111,
|
||||
TRADING_MODE_LINK:
|
||||
'https://docs.vega.xyz/testnet/concepts/trading-on-vega/trading-modes#auction-type-liquidity-monitoring',
|
||||
grepTags: '@regression @smoke @slow',
|
||||
grepFilterSpecs: true,
|
||||
grepOmitFiltered: true,
|
||||
txTimeout: { timeout: 70000 },
|
||||
},
|
||||
});
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
declare module '*.scss';
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "trading-e2e",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "apps/trading-e2e/src",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"e2e": {
|
||||
"executor": "@nx/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "apps/trading-e2e/cypress.config.js",
|
||||
"devServerTarget": "trading:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "trading:serve:production"
|
||||
},
|
||||
"live": {
|
||||
"devServerTarget": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/trading-e2e/**/*.{js,ts}"]
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"executor": "nx:run-commands",
|
||||
"outputs": [],
|
||||
"options": {
|
||||
"command": "yarn tsc --project ./apps/trading-e2e/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [],
|
||||
"implicitDependencies": ["trading"]
|
||||
}
|
||||
Vendored
-20
@@ -1,20 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
declare namespace Cypress {
|
||||
// specify additional properties in the TestConfig object
|
||||
// in our case we will add "tags" property
|
||||
interface SuiteConfigOverrides {
|
||||
/**
|
||||
* List of tags for this test
|
||||
* @example a single tag
|
||||
* it('logs in', { tags: '@smoke' }, () => { ... })
|
||||
* @example multiple tags
|
||||
* it('works', { tags: ['@smoke', '@slow'] }, () => { ... })
|
||||
*/
|
||||
tags?: string | string[];
|
||||
}
|
||||
|
||||
interface Cypress {
|
||||
grep?: (grep?: string, tags?: string, burn?: string) => void;
|
||||
}
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
import { connectEthereumWallet } from '../support/ethereum-wallet';
|
||||
import { selectAsset } from '../support/helpers';
|
||||
|
||||
const amountField = 'input[name="amount"]';
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
|
||||
const btcName = 0;
|
||||
const vegaName = 4;
|
||||
const btcSymbol = 'tBTC';
|
||||
const vegaSymbol = 'VEGA';
|
||||
const toastContent = 'toast-content';
|
||||
const depositsTab = 'Deposits';
|
||||
const toastCloseBtn = 'toast-close';
|
||||
const completeWithdrawalBtn = 'complete-withdrawal';
|
||||
const depositSubmit = 'deposit-submit';
|
||||
const approveSubmit = 'approve-submit';
|
||||
const dialogContent = 'dialog-content';
|
||||
|
||||
// Because the tests are run on a live network to optimize time, the tests are interdependent and must be run in the given order.
|
||||
describe('capsule - without MultiSign', { tags: '@slow' }, () => {
|
||||
before(() => {
|
||||
cy.createMarket();
|
||||
cy.get('@markets').then((markets) => {
|
||||
cy.wrap(markets[0]).as('market');
|
||||
});
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/portfolio');
|
||||
});
|
||||
|
||||
it('can deposit', function () {
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
|
||||
// 1001-DEPO-001
|
||||
// 1001-DEPO-002
|
||||
// 1001-DEPO-003
|
||||
// 1001-DEPO-005
|
||||
// 1001-DEPO-006
|
||||
// 1001-DEPO-007
|
||||
// 1001-DEPO-008
|
||||
// 1001-DEPO-009
|
||||
// 1001-DEPO-010
|
||||
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
selectAsset(btcName);
|
||||
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
|
||||
|
||||
cy.getByTestId('approve-default').should(
|
||||
'contain.text',
|
||||
`Before you can make a deposit of your chosen asset, ${btcSymbol}, you need to approve its use in your Ethereum wallet`
|
||||
);
|
||||
cy.getByTestId(approveSubmit).click();
|
||||
cy.getByTestId('approve-pending').should('exist');
|
||||
cy.getByTestId('approve-confirmed').should('exist');
|
||||
cy.get(amountField).focus();
|
||||
cy.get(amountField).clear().type('10');
|
||||
cy.getByTestId(depositSubmit).click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
`Transaction confirmedYour transaction has been confirmed.View on EtherscanDeposit 10.00 ${btcSymbol}`,
|
||||
{ matchCase: false }
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
cy.getByTestId('Collateral').click();
|
||||
|
||||
cy.highlight('deposit verification');
|
||||
|
||||
cy.get('[col-id="asset.symbol"]', txTimeout).should(
|
||||
'contain.text',
|
||||
btcSymbol
|
||||
);
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
|
||||
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
|
||||
|
||||
cy.get('[col-id="txHash"]')
|
||||
.should('have.length.above', 2)
|
||||
.eq(1)
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get('[col-id="asset.symbol"]').should('have.text', btcSymbol);
|
||||
cy.get('[col-id="amount"]').should('have.text', '10.00');
|
||||
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
|
||||
cy.get('[col-id="status"]').should('have.text', 'Finalized');
|
||||
cy.get('[col-id="txHash"]')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', `${sepoliaUrl}/tx/0x`);
|
||||
});
|
||||
});
|
||||
|
||||
it('can not withdrawal because of no MultiSign', function () {
|
||||
// 1002-WITH-022
|
||||
// 1002-WITH-023
|
||||
// 0003-WTXN-011
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
cy.getByTestId('withdraw-dialog-button').click();
|
||||
selectAsset(btcName);
|
||||
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
|
||||
cy.get(amountField).focus();
|
||||
cy.get(amountField).clear().type('1');
|
||||
cy.getByTestId('submit-withdrawal').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
'Funds unlocked'
|
||||
);
|
||||
// cy.getByTestId(toastCloseBtn).click();
|
||||
cy.highlight('withdrawals verification');
|
||||
cy.getByTestId('toast-complete-withdrawal').last().click();
|
||||
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
'Error occurredcannot estimate gas'
|
||||
);
|
||||
cy.getByTestId(completeWithdrawalBtn).should(
|
||||
'contain.text',
|
||||
'Complete withdrawal'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('capsule', { tags: '@slow', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.updateCapsuleMultiSig();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.createMarket();
|
||||
cy.get('@markets').then((markets) => {
|
||||
cy.wrap(markets[0]).as('market');
|
||||
});
|
||||
cy.setOnBoardingViewed();
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
|
||||
it('can withdrawal', function () {
|
||||
// 1002-WITH-0014
|
||||
// 1002-WITH-006
|
||||
// 1002-WITH-009
|
||||
// 1002-WITH-011
|
||||
// 1002-WITH-024
|
||||
// 1002-WITH-012
|
||||
// 1002-WITH-013
|
||||
// 1002-WITH-014
|
||||
// 1002-WITH-015
|
||||
// 1002-WITH-016
|
||||
// 1002-WITH-017
|
||||
// 1002-WITH-019
|
||||
// 1002-WITH-020
|
||||
// 1002-WITH-021
|
||||
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
cy.getByTestId(toastCloseBtn, txTimeout).click();
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
cy.getByTestId('withdraw-dialog-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
selectAsset(btcName);
|
||||
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
|
||||
cy.get(amountField).clear().type('1');
|
||||
cy.getByTestId('submit-withdrawal').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
'Funds unlocked'
|
||||
);
|
||||
|
||||
cy.highlight('withdrawals verification');
|
||||
cy.getByTestId('toast-complete-withdrawal').click();
|
||||
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
'Transaction confirmed'
|
||||
);
|
||||
cy.getByTestId(toastContent, txTimeout)
|
||||
.should('contain.text', 'Funds unlocked')
|
||||
.and('contain.text', 'Your funds have been unlocked for withdrawal.')
|
||||
.and(
|
||||
'contain.text',
|
||||
'View in block explorerYou can save your withdrawal details for extra security.'
|
||||
)
|
||||
.and('contain.text', 'Withdraw 1.00 tBTCComplete withdrawal');
|
||||
cy.getByTestId('toast-withdrawal-details').click();
|
||||
cy.getByTestId(dialogContent)
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.getByTestId('dialog-title').should(
|
||||
'contain.text',
|
||||
'Save withdrawal details'
|
||||
);
|
||||
cy.getByTestId('copy-button').should('be.visible');
|
||||
cy.getByTestId('assetSource_value').should(
|
||||
'have.text',
|
||||
'0xb63D135B0a6854EEb765d69ca36210cC70BECAE0'
|
||||
);
|
||||
cy.getByTestId('amount_value').should('have.text', '100000');
|
||||
cy.getByTestId('nonce_value').invoke('text').should('not.be.empty');
|
||||
cy.getByTestId('signatures_value')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('targetAddress_value').should(
|
||||
'have.text',
|
||||
ethWalletAddress
|
||||
);
|
||||
cy.getByTestId('creation_value').invoke('text').should('not.be.empty');
|
||||
});
|
||||
cy.getByTestId('close-withdrawal-approval-dialog').click();
|
||||
|
||||
cy.get('.ag-center-cols-container')
|
||||
.find('[col-id="status"]')
|
||||
.eq(0, txTimeout)
|
||||
.should('contain.text', 'Completed');
|
||||
|
||||
cy.get('[col-id="txHash"]', txTimeout)
|
||||
.should('have.length.above', 1)
|
||||
.eq(1)
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get('[col-id="asset.symbol"]').should('have.text', btcSymbol);
|
||||
cy.get('[col-id="amount"]').should('have.text', '1.00');
|
||||
cy.get('[col-id="details.receiverAddress"]')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', `${sepoliaUrl}/address/`);
|
||||
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
|
||||
cy.get('[col-id="withdrawnTimestamp"]').should('not.be.empty');
|
||||
cy.get('[col-id="status"]').should('have.text', 'Completed');
|
||||
cy.get('[col-id="txHash"]')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', `${sepoliaUrl}/tx/0x`);
|
||||
});
|
||||
|
||||
cy.getByTestId('withdraw-dialog-button').click({ force: true });
|
||||
// cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '6.999');
|
||||
});
|
||||
|
||||
it('approved amount is less than deposit', function () {
|
||||
// 1001-DEPO-006
|
||||
// 1001-DEPO-007
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
cy.getByTestId(toastCloseBtn, txTimeout).click();
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
selectAsset(btcName);
|
||||
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
|
||||
cy.contains('Deposits of tBTC not approved').should('not.exist');
|
||||
cy.contains('Use maximum').should('be.visible');
|
||||
cy.get(amountField).clear().type('20000000');
|
||||
cy.getByTestId(depositSubmit).should('be.visible');
|
||||
cy.getByTestId(depositSubmit).click();
|
||||
cy.getByTestId('input-error-text').should(
|
||||
'contain.text',
|
||||
`You can't deposit more than you have in your Ethereum wallet`
|
||||
);
|
||||
});
|
||||
|
||||
it('withdraw - delay verification', function () {
|
||||
// 1001-DEPO-024
|
||||
// 1002-WITH-007
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('[data-testid="pathname-/portfolio"]', txTimeout).should('exist');
|
||||
cy.getByTestId(toastCloseBtn, txTimeout).click();
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
selectAsset(vegaName);
|
||||
cy.getByTestId('approve-submit').click();
|
||||
cy.getByTestId('approve-confirmed').should(
|
||||
'contain.text',
|
||||
'You approved deposits of up to VEGA'
|
||||
);
|
||||
cy.get(amountField).clear().type('10000');
|
||||
cy.getByTestId('deposit-submit').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
`Your transaction has been confirmed.`,
|
||||
{ matchCase: false }
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click({ multiple: true });
|
||||
cy.getByTestId('Collateral').click();
|
||||
|
||||
cy.highlight('deposit verification');
|
||||
|
||||
cy.get('[col-id="asset.symbol"]', txTimeout).should(
|
||||
'contain.text',
|
||||
vegaSymbol
|
||||
);
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.get('.ag-cell-value', txTimeout).should('contain.text', vegaSymbol);
|
||||
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
|
||||
|
||||
cy.get('[col-id="txHash"]')
|
||||
.should('have.length.above', 2)
|
||||
.eq(1)
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get('[col-id="asset.symbol"]').should('have.text', vegaSymbol);
|
||||
cy.get('[col-id="amount"]').should('have.text', '10,000.00');
|
||||
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
|
||||
cy.get('[col-id="status"]').should('have.text', 'Finalized');
|
||||
cy.get('[col-id="txHash"]')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', `${sepoliaUrl}/tx/0x`);
|
||||
});
|
||||
|
||||
cy.getByTestId('Withdrawals').click(txTimeout);
|
||||
cy.getByTestId('withdraw-dialog-button').click();
|
||||
selectAsset(1);
|
||||
cy.get(amountField).clear().type('10000');
|
||||
cy.getByTestId('DELAY_TIME_value').should('have.text', '5 days');
|
||||
cy.getByTestId('submit-withdrawal').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
'Your funds have been unlocked'
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
cy.getByTestId(completeWithdrawalBtn).first().should('be.visible').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should('contain.text', 'Delayed');
|
||||
cy.getByTestId('tab-withdrawals').within(() => {
|
||||
cy.get('.ag-center-cols-container')
|
||||
.children()
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('[col-id="status"]').contains(
|
||||
/Delayed \(ready in (\d{1,2}:\d{2}:\d{2}:\d{2})\)/
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
import { connectEthereumWallet } from '../support/ethereum-wallet';
|
||||
|
||||
const connectEthWalletBtn = 'connect-eth-wallet-btn';
|
||||
|
||||
describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
});
|
||||
|
||||
it('can connect', () => {
|
||||
// 0004-EWAL-001
|
||||
|
||||
cy.getByTestId('Deposits').click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
cy.getByTestId('connect-eth-wallet-btn').click();
|
||||
cy.getByTestId('web3-connector-list').should('exist');
|
||||
cy.getByTestId('web3-connector-MetaMask').click();
|
||||
cy.getByTestId('web3-connector-list').should('not.exist');
|
||||
cy.getByTestId('tab-deposits').should('not.be.empty');
|
||||
});
|
||||
|
||||
it('able to disconnect eth wallet', () => {
|
||||
// 0004-EWAL-004
|
||||
// 0004-EWAL-005
|
||||
// 0004-EWAL-006
|
||||
|
||||
cy.getByTestId('Deposits').click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('MetaMask');
|
||||
cy.getByTestId('ethereum-address').should('have.text', '0xEe7D…d94F');
|
||||
cy.getByTestId('disconnect-ethereum-wallet')
|
||||
.should('have.text', 'Disconnect')
|
||||
.click();
|
||||
cy.getByTestId(connectEthWalletBtn).should('exist');
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
import { OrderType } from '@vegaprotocol/types';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
|
||||
const orderSizeField = 'order-size';
|
||||
const orderPriceField = 'order-price';
|
||||
const orderTIFDropDown = 'order-tif';
|
||||
const placeOrderBtn = 'place-order';
|
||||
|
||||
export const createOrder = (order: OrderSubmission): void => {
|
||||
cy.log('Placing order', order);
|
||||
const { type, side, size, price, timeInForce, expiresAt } = order;
|
||||
|
||||
cy.getByTestId(
|
||||
`order-type-${type === OrderType.TYPE_LIMIT ? 'Limit' : 'Market'}`
|
||||
).click();
|
||||
cy.getByTestId(`order-side-${side}`).click();
|
||||
cy.getByTestId(orderSizeField).clear().type(size);
|
||||
if (price) {
|
||||
cy.getByTestId(orderPriceField).clear().type(price);
|
||||
}
|
||||
cy.getByTestId(orderTIFDropDown).select(timeInForce);
|
||||
if (timeInForce === 'TIME_IN_FORCE_GTT') {
|
||||
if (!expiresAt) {
|
||||
throw new Error('Specify expiresAt if using GTT');
|
||||
}
|
||||
cy.getByTestId('date-picker-field').type(expiresAt);
|
||||
}
|
||||
cy.getByTestId(placeOrderBtn).click();
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
export const orderSizeField = 'order-size';
|
||||
export const orderPriceField = 'order-price';
|
||||
export const orderTIFDropDown = 'order-tif';
|
||||
export const placeOrderBtn = 'place-order';
|
||||
export const toggleShort = 'order-side-SIDE_SELL';
|
||||
export const toggleLong = 'order-side-SIDE_BUY';
|
||||
export const toggleLimit = 'order-type-Limit';
|
||||
export const toggleMarket = 'order-type-Market';
|
||||
|
||||
export const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
|
||||
return {
|
||||
code: Schema.OrderTimeInForceCode[value],
|
||||
value,
|
||||
text: Schema.OrderTimeInForceMapping[value],
|
||||
};
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
export const connectEthereumWallet = (connectorName: string) => {
|
||||
cy.getByTestId('connect-eth-wallet-btn').should('be.enabled').click();
|
||||
cy.getByTestId('web3-connector-list').should('be.visible');
|
||||
cy.getByTestId(`web3-connector-${connectorName}`).click();
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
export const selectAsset = (assetIndex: number) => {
|
||||
cy.log(`selecting asset: ${assetIndex}`);
|
||||
cy.getByTestId('select-asset').click();
|
||||
cy.get('[data-testid="rich-select-option"]').eq(assetIndex).click();
|
||||
|
||||
// The asset only gets set once the queries (getWithdrawThreshold, getDelay)
|
||||
// against the Ethereum change resolve, we should fix this but for now just force
|
||||
// some wait time
|
||||
// eslint-disable-next-line
|
||||
cy.wait(100);
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
import '@vegaprotocol/cypress';
|
||||
import 'cypress-real-events/support';
|
||||
import registerCypressGrep from '@cypress/grep';
|
||||
import { addMockTradingPage } from './trading';
|
||||
import 'cypress-mochawesome-reporter/register';
|
||||
|
||||
registerCypressGrep();
|
||||
addMockTradingPage();
|
||||
@@ -1,36 +0,0 @@
|
||||
import type {
|
||||
OrdersUpdateSubscription,
|
||||
OrdersUpdateSubscriptionVariables,
|
||||
OrderUpdateFieldsFragment,
|
||||
} from '@vegaprotocol/orders';
|
||||
import type { onMessage } from '@vegaprotocol/cypress';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import { orderUpdateSubscription } from '@vegaprotocol/mock';
|
||||
|
||||
const sendOrderUpdate: ((data: OrdersUpdateSubscription) => void)[] = [];
|
||||
const getOnOrderUpdate = () => {
|
||||
const onOrderUpdate: onMessage<
|
||||
OrdersUpdateSubscription,
|
||||
OrdersUpdateSubscriptionVariables
|
||||
> = (send) => {
|
||||
sendOrderUpdate.push(send);
|
||||
};
|
||||
return onOrderUpdate;
|
||||
};
|
||||
|
||||
export const getSubscriptionMocks = () => ({
|
||||
OrdersUpdate: getOnOrderUpdate(),
|
||||
});
|
||||
|
||||
export function updateOrder(
|
||||
override?: PartialDeep<OrderUpdateFieldsFragment>
|
||||
): void {
|
||||
const update: OrdersUpdateSubscription = orderUpdateSubscription({
|
||||
// @ts-ignore partial deep check failing
|
||||
orders: [override],
|
||||
});
|
||||
if (!sendOrderUpdate) {
|
||||
throw new Error('OrderSub not called');
|
||||
}
|
||||
sendOrderUpdate.forEach((send) => send(update));
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import type {
|
||||
OrderAmendment,
|
||||
OrderAmendmentBody,
|
||||
OrderCancellation,
|
||||
OrderCancellationBody,
|
||||
OrderSubmission,
|
||||
OrderSubmissionBody,
|
||||
Transaction,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
export const testOrderSubmission = (
|
||||
order: OrderSubmission,
|
||||
expected?: Partial<OrderSubmission>
|
||||
) => {
|
||||
const expectedOrder = {
|
||||
...order,
|
||||
...expected,
|
||||
};
|
||||
|
||||
const transaction: OrderSubmissionBody = {
|
||||
orderSubmission: expectedOrder,
|
||||
};
|
||||
vegaWalletTransaction(transaction);
|
||||
verifyToast();
|
||||
};
|
||||
|
||||
export const testOrderAmendment = (
|
||||
order: OrderAmendment,
|
||||
expected?: Partial<OrderAmendment>
|
||||
) => {
|
||||
const expectedOrder = {
|
||||
...order,
|
||||
...expected,
|
||||
};
|
||||
|
||||
const transaction: OrderAmendmentBody = {
|
||||
orderAmendment: expectedOrder,
|
||||
};
|
||||
vegaWalletTransaction(transaction);
|
||||
verifyToast();
|
||||
};
|
||||
|
||||
export const testOrderCancellation = (
|
||||
order: OrderCancellation,
|
||||
expected?: Partial<OrderCancellation>
|
||||
) => {
|
||||
const expectedOrder = {
|
||||
...order,
|
||||
...expected,
|
||||
};
|
||||
|
||||
const transaction: OrderCancellationBody = {
|
||||
orderCancellation: expectedOrder,
|
||||
};
|
||||
vegaWalletTransaction(transaction);
|
||||
verifyToast();
|
||||
};
|
||||
|
||||
const vegaWalletTransaction = (transaction: Transaction) => {
|
||||
cy.wait('@VegaWalletTransaction')
|
||||
.its('request')
|
||||
.then((req) => {
|
||||
expect(req.body.params).to.deep.equal({
|
||||
publicKey: Cypress.env('VEGA_PUBLIC_KEY'),
|
||||
sendingMode: 'TYPE_SYNC',
|
||||
transaction,
|
||||
});
|
||||
expect(req.headers.authorization).to.equal(
|
||||
`VWT ${Cypress.env('VEGA_WALLET_API_TOKEN')}`
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const verifyToast = () => {
|
||||
cy.getByTestId('toast').should('contain.text', 'Awaiting confirmation');
|
||||
cy.getByTestId('toast')
|
||||
.find('a')
|
||||
.invoke('attr', 'href')
|
||||
.should('include', `${Cypress.env('EXPLORER_URL')}/txs/test-tx-hash`);
|
||||
cy.getByTestId('toast-close').click();
|
||||
};
|
||||
@@ -1,252 +0,0 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { CyHttpMessages } from 'cypress/types/net-stubbing';
|
||||
import type { Provider, Status } from '@vegaprotocol/markets';
|
||||
import {
|
||||
accountsQuery,
|
||||
assetQuery,
|
||||
assetsQuery,
|
||||
candlesQuery,
|
||||
chartQuery,
|
||||
depositsQuery,
|
||||
estimateFeesQuery,
|
||||
marginsQuery,
|
||||
marketCandlesQuery,
|
||||
marketDataQuery,
|
||||
marketDepthQuery,
|
||||
marketInfoQuery,
|
||||
marketsCandlesQuery,
|
||||
marketsDataQuery,
|
||||
marketsQuery,
|
||||
networkParamsQuery,
|
||||
nodeGuardQuery,
|
||||
ordersQuery,
|
||||
estimatePositionQuery,
|
||||
positionsQuery,
|
||||
proposalListQuery,
|
||||
tradesQuery,
|
||||
withdrawalsQuery,
|
||||
protocolUpgradeProposalsQuery,
|
||||
blockStatisticsQuery,
|
||||
networkParamQuery,
|
||||
liquidityProvisionsQuery,
|
||||
successorMarketQuery,
|
||||
parentMarketIdQuery,
|
||||
successorMarketIdsQuery,
|
||||
successorMarketProposalDetailsQuery,
|
||||
liquidityProvidersQuery,
|
||||
} from '@vegaprotocol/mock';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/markets';
|
||||
|
||||
type MarketPageMockData = {
|
||||
state: Schema.MarketState;
|
||||
tradingMode?: Schema.MarketTradingMode;
|
||||
trigger?: Schema.AuctionTrigger;
|
||||
};
|
||||
|
||||
const ORACLE_PUBKEY = Cypress.env('ORACLE_PUBKEY');
|
||||
|
||||
const marketDataOverride = (
|
||||
data: MarketPageMockData
|
||||
): PartialDeep<MarketDataQuery> => ({
|
||||
marketsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
data: {
|
||||
// @ts-ignore conflict between incoming and outgoing types
|
||||
trigger: data.trigger,
|
||||
// @ts-ignore same as above
|
||||
marketTradingMode: data.tradingMode,
|
||||
marketState: data.state,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const marketsDataOverride = (
|
||||
data: MarketPageMockData
|
||||
): PartialDeep<MarketsQuery> => ({
|
||||
marketsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
// @ts-ignore conflict between incoming and outgoing types
|
||||
tradingMode: data.tradingMode,
|
||||
state: data.state,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const mockTradingPage = (
|
||||
req: CyHttpMessages.IncomingHttpRequest,
|
||||
state: Schema.MarketState = Schema.MarketState.STATE_ACTIVE,
|
||||
tradingMode?: Schema.MarketTradingMode,
|
||||
trigger?: Schema.AuctionTrigger
|
||||
) => {
|
||||
aliasGQLQuery(req, 'NodeGuard', nodeGuardQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'Markets',
|
||||
marketsQuery(marketsDataOverride({ state, tradingMode, trigger }))
|
||||
);
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'MarketData',
|
||||
marketDataQuery(marketDataOverride({ state, tradingMode, trigger }))
|
||||
);
|
||||
aliasGQLQuery(req, 'MarketsData', marketsDataQuery());
|
||||
aliasGQLQuery(req, 'MarketsCandles', marketsCandlesQuery());
|
||||
aliasGQLQuery(req, 'MarketCandles', marketCandlesQuery());
|
||||
aliasGQLQuery(req, 'MarketDepth', marketDepthQuery());
|
||||
aliasGQLQuery(req, 'Orders', ordersQuery());
|
||||
aliasGQLQuery(req, 'Accounts', accountsQuery());
|
||||
aliasGQLQuery(req, 'Positions', positionsQuery());
|
||||
aliasGQLQuery(req, 'Margins', marginsQuery());
|
||||
aliasGQLQuery(req, 'Assets', assetsQuery());
|
||||
aliasGQLQuery(req, 'Asset', assetQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'MarketInfo',
|
||||
marketInfoQuery({
|
||||
market: {
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
dataSourceSpecForSettlementData: {
|
||||
data: {
|
||||
sourceType: {
|
||||
sourceType: {
|
||||
signers: [
|
||||
{
|
||||
__typename: 'Signer',
|
||||
signer: {
|
||||
__typename: 'PubKey',
|
||||
key: ORACLE_PUBKEY,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
data: {
|
||||
sourceType: {
|
||||
sourceType: {
|
||||
signers: [
|
||||
{
|
||||
__typename: 'Signer',
|
||||
signer: {
|
||||
__typename: 'PubKey',
|
||||
key: ORACLE_PUBKEY,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
aliasGQLQuery(req, 'Trades', tradesQuery());
|
||||
aliasGQLQuery(req, 'Chart', chartQuery());
|
||||
aliasGQLQuery(req, 'LiquidityProvisions', liquidityProvisionsQuery());
|
||||
aliasGQLQuery(req, 'LiquidityProviders', liquidityProvidersQuery());
|
||||
aliasGQLQuery(req, 'Candles', candlesQuery());
|
||||
aliasGQLQuery(req, 'Withdrawals', withdrawalsQuery());
|
||||
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
|
||||
aliasGQLQuery(req, 'NetworkParam', networkParamQuery);
|
||||
aliasGQLQuery(req, 'EstimateFees', estimateFeesQuery());
|
||||
aliasGQLQuery(req, 'EstimatePosition', estimatePositionQuery());
|
||||
aliasGQLQuery(req, 'ProposalsList', proposalListQuery());
|
||||
aliasGQLQuery(req, 'Deposits', depositsQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'ProtocolUpgradeProposals',
|
||||
protocolUpgradeProposalsQuery()
|
||||
);
|
||||
aliasGQLQuery(req, 'BlockStatistics', blockStatisticsQuery());
|
||||
aliasGQLQuery(req, 'SuccessorMarket', successorMarketQuery());
|
||||
aliasGQLQuery(req, 'ParentMarketId', parentMarketIdQuery());
|
||||
aliasGQLQuery(req, 'SuccessorMarketIds', successorMarketIdsQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'SuccessorMarketProposalDetails',
|
||||
successorMarketProposalDetailsQuery()
|
||||
);
|
||||
};
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
mockTradingPage(
|
||||
state?: Schema.MarketState,
|
||||
tradingMode?: Schema.MarketTradingMode,
|
||||
trigger?: Schema.AuctionTrigger,
|
||||
oracleStatus?: Status
|
||||
): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const addMockTradingPage = () => {
|
||||
Cypress.Commands.add(
|
||||
'mockTradingPage',
|
||||
(
|
||||
state = Schema.MarketState.STATE_ACTIVE,
|
||||
tradingMode,
|
||||
trigger,
|
||||
oracleStatus
|
||||
) => {
|
||||
cy.mockChainId();
|
||||
cy.mockGQL((req) => {
|
||||
mockTradingPage(req, state, tradingMode, trigger);
|
||||
});
|
||||
|
||||
const oracle: Provider = {
|
||||
name: 'Another oracle',
|
||||
url: 'https://zombo.com',
|
||||
description_markdown:
|
||||
'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n',
|
||||
oracle: {
|
||||
status: oracleStatus || 'GOOD',
|
||||
status_reason: '',
|
||||
first_verified: '2022-01-01T00:00:00.000Z',
|
||||
last_verified: '2022-12-31T00:00:00.000Z',
|
||||
type: 'public_key',
|
||||
public_key: ORACLE_PUBKEY,
|
||||
},
|
||||
proofs: [
|
||||
{
|
||||
format: 'signed_message',
|
||||
available: true,
|
||||
type: 'public_key',
|
||||
public_key: ORACLE_PUBKEY,
|
||||
message: 'SOMEHEX',
|
||||
},
|
||||
],
|
||||
github_link: `https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/public_key-${ORACLE_PUBKEY}.toml`,
|
||||
};
|
||||
// Prevent request to github, return some dummy content
|
||||
cy.intercept(
|
||||
'GET',
|
||||
/^https:\/\/raw.githubusercontent.com\/vegaprotocol\/well-known/,
|
||||
{
|
||||
body: [oracle],
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"sourceMap": false,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"allowJs": true,
|
||||
"types": ["cypress", "node", "cypress-real-events", "@cypress/grep"],
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "./declaration.d.ts"]
|
||||
}
|
||||
@@ -49,10 +49,4 @@ To run the minimal set of unit tests, run the following:
|
||||
yarn nx test trading
|
||||
```
|
||||
|
||||
To run the UI automation tests with a mocked API, run:
|
||||
|
||||
```bash
|
||||
yarn nx run trading-e2e:e2e
|
||||
```
|
||||
|
||||
To run tests with market sim please read [the readme](e2e/README.md).
|
||||
To run the UI automation tests please read [e2e/README.md](e2e/README.md)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { ProfileDialog } from './profile-dialog';
|
||||
@@ -0,0 +1,150 @@
|
||||
import {
|
||||
Dialog,
|
||||
FormGroup,
|
||||
Input,
|
||||
InputError,
|
||||
Intent,
|
||||
TradingButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useProfileDialogStore } from '../../stores/profile-dialog-store';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { useRequired } from '@vegaprotocol/utils';
|
||||
import {
|
||||
useSimpleTransaction,
|
||||
type Status,
|
||||
useVegaWallet,
|
||||
} from '@vegaprotocol/wallet-react';
|
||||
import {
|
||||
usePartyProfilesQuery,
|
||||
type PartyProfilesQuery,
|
||||
} from '../vega-wallet-connect-button/__generated__/PartyProfiles';
|
||||
|
||||
export const ProfileDialog = () => {
|
||||
const t = useT();
|
||||
const { pubKeys } = useVegaWallet();
|
||||
const { data, refetch } = usePartyProfilesQuery({
|
||||
variables: { partyIds: pubKeys.map((pk) => pk.publicKey) },
|
||||
skip: pubKeys.length <= 0,
|
||||
});
|
||||
const open = useProfileDialogStore((store) => store.open);
|
||||
const pubKey = useProfileDialogStore((store) => store.pubKey);
|
||||
const setOpen = useProfileDialogStore((store) => store.setOpen);
|
||||
|
||||
const { send, status, error, reset } = useSimpleTransaction({
|
||||
onSuccess: () => {
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
|
||||
const profileEdge = data?.partiesProfilesConnection?.edges.find(
|
||||
(e) => e.node.partyId === pubKey
|
||||
);
|
||||
|
||||
const sendTx = (field: FormFields) => {
|
||||
send({
|
||||
updatePartyProfile: {
|
||||
alias: field.alias,
|
||||
metadata: [],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onChange={() => {
|
||||
setOpen(undefined);
|
||||
reset();
|
||||
}}
|
||||
title={t('Edit profile')}
|
||||
>
|
||||
<ProfileForm
|
||||
profile={profileEdge?.node}
|
||||
status={status}
|
||||
error={error}
|
||||
onSubmit={sendTx}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
interface FormFields {
|
||||
alias: string;
|
||||
}
|
||||
|
||||
type Profile = NonNullable<
|
||||
PartyProfilesQuery['partiesProfilesConnection']
|
||||
>['edges'][number]['node'];
|
||||
|
||||
const ProfileForm = ({
|
||||
profile,
|
||||
onSubmit,
|
||||
status,
|
||||
error,
|
||||
}: {
|
||||
profile: Profile | undefined;
|
||||
onSubmit: (fields: FormFields) => void;
|
||||
status: Status;
|
||||
error: string | undefined;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const required = useRequired();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<FormFields>({
|
||||
defaultValues: {
|
||||
alias: profile?.alias,
|
||||
},
|
||||
});
|
||||
|
||||
const renderButtonText = () => {
|
||||
if (status === 'requested') {
|
||||
return t('Confirm in wallet...');
|
||||
}
|
||||
|
||||
if (status === 'pending') {
|
||||
return t('Confirming transaction...');
|
||||
}
|
||||
|
||||
return t('Submit');
|
||||
};
|
||||
|
||||
const errorMessage = errors.alias?.message || error;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="mt-3">
|
||||
<FormGroup label="Alias" labelFor="alias">
|
||||
<Input
|
||||
{...register('alias', {
|
||||
validate: {
|
||||
required,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<InputError>
|
||||
<p className="break-words max-w-full first-letter:uppercase">
|
||||
{errorMessage}
|
||||
</p>
|
||||
</InputError>
|
||||
)}
|
||||
|
||||
{status === 'confirmed' && (
|
||||
<p className="mt-2 mb-4 text-sm text-success">
|
||||
{t('Profile updated')}
|
||||
</p>
|
||||
)}
|
||||
</FormGroup>
|
||||
<TradingButton
|
||||
type="submit"
|
||||
intent={Intent.Info}
|
||||
disabled={status === 'requested' || status === 'pending'}
|
||||
>
|
||||
{renderButtonText()}
|
||||
</TradingButton>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
query PartyProfiles($partyIds: [ID!]) {
|
||||
partiesProfilesConnection(ids: $partyIds) {
|
||||
edges {
|
||||
node {
|
||||
partyId
|
||||
alias
|
||||
metadata {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type PartyProfilesQueryVariables = Types.Exact<{
|
||||
partyIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type PartyProfilesQuery = { __typename?: 'Query', partiesProfilesConnection?: { __typename?: 'PartiesProfilesConnection', edges: Array<{ __typename?: 'PartyProfileEdge', node: { __typename?: 'PartyProfile', partyId: string, alias: string, metadata: Array<{ __typename?: 'Metadata', key: string, value: string }> } }> } | null };
|
||||
|
||||
|
||||
export const PartyProfilesDocument = gql`
|
||||
query PartyProfiles($partyIds: [ID!]) {
|
||||
partiesProfilesConnection(ids: $partyIds) {
|
||||
edges {
|
||||
node {
|
||||
partyId
|
||||
alias
|
||||
metadata {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __usePartyProfilesQuery__
|
||||
*
|
||||
* To run a query within a React component, call `usePartyProfilesQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `usePartyProfilesQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = usePartyProfilesQuery({
|
||||
* variables: {
|
||||
* partyIds: // value for 'partyIds'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function usePartyProfilesQuery(baseOptions?: Apollo.QueryHookOptions<PartyProfilesQuery, PartyProfilesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<PartyProfilesQuery, PartyProfilesQueryVariables>(PartyProfilesDocument, options);
|
||||
}
|
||||
export function usePartyProfilesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PartyProfilesQuery, PartyProfilesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<PartyProfilesQuery, PartyProfilesQueryVariables>(PartyProfilesDocument, options);
|
||||
}
|
||||
export type PartyProfilesQueryHookResult = ReturnType<typeof usePartyProfilesQuery>;
|
||||
export type PartyProfilesLazyQueryHookResult = ReturnType<typeof usePartyProfilesLazyQuery>;
|
||||
export type PartyProfilesQueryResult = Apollo.QueryResult<PartyProfilesQuery, PartyProfilesQueryVariables>;
|
||||
+52
-12
@@ -1,21 +1,57 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { act, fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { VegaWalletConnectButton } from './vega-wallet-connect-button';
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import {
|
||||
mockConfig,
|
||||
MockedWalletProvider,
|
||||
} from '@vegaprotocol/wallet-react/testing';
|
||||
import { MockedProvider, type MockedResponse } from '@apollo/react-testing';
|
||||
import {
|
||||
PartyProfilesDocument,
|
||||
type PartyProfilesQuery,
|
||||
} from './__generated__/PartyProfiles';
|
||||
|
||||
jest.mock('../../lib/hooks/use-get-current-route-id', () => ({
|
||||
useGetCurrentRouteId: jest.fn().mockReturnValue('current-route-id'),
|
||||
}));
|
||||
|
||||
const key = { publicKey: '123456__123456', name: 'test' };
|
||||
const key2 = { publicKey: 'abcdef__abcdef', name: 'test2' };
|
||||
const keys = [key, key2];
|
||||
const keyProfile = {
|
||||
__typename: 'PartyProfile' as const,
|
||||
partyId: key.publicKey,
|
||||
alias: `${key.name} alias`,
|
||||
metadata: [],
|
||||
};
|
||||
|
||||
const renderComponent = (mockOnClick = jest.fn()) => {
|
||||
const partyProfilesMock: MockedResponse<PartyProfilesQuery> = {
|
||||
request: {
|
||||
query: PartyProfilesDocument,
|
||||
variables: { partyIds: keys.map((k) => k.publicKey) },
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
partiesProfilesConnection: {
|
||||
__typename: 'PartiesProfilesConnection',
|
||||
edges: [
|
||||
{
|
||||
__typename: 'PartyProfileEdge',
|
||||
node: keyProfile,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<MockedWalletProvider>
|
||||
<VegaWalletConnectButton onClick={mockOnClick} />
|
||||
</MockedWalletProvider>
|
||||
<MockedProvider mocks={[partyProfilesMock]}>
|
||||
<MockedWalletProvider>
|
||||
<VegaWalletConnectButton onClick={mockOnClick} />
|
||||
</MockedWalletProvider>
|
||||
</MockedProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -43,10 +79,6 @@ describe('VegaWalletConnectButton', () => {
|
||||
});
|
||||
|
||||
it('should open dropdown and refresh keys when connected', async () => {
|
||||
const key = { publicKey: '123456__123456', name: 'test' };
|
||||
const key2 = { publicKey: 'abcdef__abcdef', name: 'test2' };
|
||||
const keys = [key, key2];
|
||||
|
||||
mockConfig.store.setState({
|
||||
status: 'connected',
|
||||
keys,
|
||||
@@ -61,14 +93,22 @@ describe('VegaWalletConnectButton', () => {
|
||||
|
||||
expect(screen.queryByTestId('connect-vega-wallet')).not.toBeInTheDocument();
|
||||
const button = screen.getByTestId('manage-vega-wallet');
|
||||
expect(button).toHaveTextContent(truncateByChars(key.publicKey));
|
||||
expect(button).toHaveTextContent(key.name);
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(await screen.findByRole('menu')).toBeInTheDocument();
|
||||
expect(await screen.findAllByRole('menuitemradio')).toHaveLength(
|
||||
keys.length
|
||||
const menuItems = await screen.findAllByRole('menuitemradio');
|
||||
expect(menuItems).toHaveLength(keys.length);
|
||||
|
||||
expect(within(menuItems[0]).getByTestId('alias')).toHaveTextContent(
|
||||
keyProfile.alias
|
||||
);
|
||||
|
||||
expect(within(menuItems[1]).getByTestId('alias')).toHaveTextContent(
|
||||
'No alias'
|
||||
);
|
||||
|
||||
expect(refreshKeys).toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(screen.getByTestId(`key-${key2.publicKey}`));
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TradingDropdownItem,
|
||||
TradingDropdownRadioItem,
|
||||
TradingDropdownItemIndicator,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { isBrowserWalletInstalled, type Key } from '@vegaprotocol/wallet';
|
||||
import { useDialogStore, useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
@@ -22,6 +23,8 @@ import classNames from 'classnames';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { usePartyProfilesQuery } from './__generated__/PartyProfiles';
|
||||
import { useProfileDialogStore } from '../../stores/profile-dialog-store';
|
||||
|
||||
export const VegaWalletConnectButton = ({
|
||||
intent = Intent.None,
|
||||
@@ -68,10 +71,10 @@ export const VegaWalletConnectButton = ({
|
||||
{activeKey ? (
|
||||
<>
|
||||
{activeKey && (
|
||||
<span className="uppercase">{activeKey.name}</span>
|
||||
<span className="uppercase">
|
||||
{activeKey.name ? activeKey.name : t('Unnamed key')}
|
||||
</span>
|
||||
)}
|
||||
{' | '}
|
||||
{truncateByChars(activeKey.publicKey)}
|
||||
</>
|
||||
) : (
|
||||
<>{'Select key'}</>
|
||||
@@ -88,20 +91,11 @@ export const VegaWalletConnectButton = ({
|
||||
onEscapeKeyDown={() => setDropdownOpen(false)}
|
||||
>
|
||||
<div className="min-w-[340px]" data-testid="keypair-list">
|
||||
<TradingDropdownRadioGroup
|
||||
value={pubKey || undefined}
|
||||
onValueChange={(value) => {
|
||||
selectPubKey(value);
|
||||
}}
|
||||
>
|
||||
{pubKeys.map((pk) => (
|
||||
<KeypairItem
|
||||
key={pk.publicKey}
|
||||
pk={pk}
|
||||
active={pk.publicKey === pubKey}
|
||||
/>
|
||||
))}
|
||||
</TradingDropdownRadioGroup>
|
||||
<KeypairRadioGroup
|
||||
pubKey={pubKey}
|
||||
pubKeys={pubKeys}
|
||||
onSelect={selectPubKey}
|
||||
/>
|
||||
<TradingDropdownSeparator />
|
||||
{!isReadOnly && (
|
||||
<TradingDropdownItem
|
||||
@@ -141,28 +135,52 @@ export const VegaWalletConnectButton = ({
|
||||
);
|
||||
};
|
||||
|
||||
const KeypairItem = ({ pk, active }: { pk: Key; active: boolean }) => {
|
||||
const KeypairRadioGroup = ({
|
||||
pubKey,
|
||||
pubKeys,
|
||||
onSelect,
|
||||
}: {
|
||||
pubKey: string | undefined;
|
||||
pubKeys: Key[];
|
||||
onSelect: (pubKey: string) => void;
|
||||
}) => {
|
||||
const { data } = usePartyProfilesQuery({
|
||||
variables: { partyIds: pubKeys.map((pk) => pk.publicKey) },
|
||||
skip: pubKeys.length <= 0,
|
||||
});
|
||||
|
||||
return (
|
||||
<TradingDropdownRadioGroup value={pubKey} onValueChange={onSelect}>
|
||||
{pubKeys.map((pk) => {
|
||||
const profile = data?.partiesProfilesConnection?.edges.find(
|
||||
(e) => e.node.partyId === pk.publicKey
|
||||
);
|
||||
return (
|
||||
<KeypairItem key={pk.publicKey} pk={pk} alias={profile?.node.alias} />
|
||||
);
|
||||
})}
|
||||
</TradingDropdownRadioGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const KeypairItem = ({ pk, alias }: { pk: Key; alias: string | undefined }) => {
|
||||
const t = useT();
|
||||
const [copied, setCopied] = useCopyTimeout();
|
||||
const setOpen = useProfileDialogStore((store) => store.setOpen);
|
||||
|
||||
return (
|
||||
<TradingDropdownRadioItem value={pk.publicKey}>
|
||||
<div
|
||||
className={classNames('flex-1 mr-2', {
|
||||
'text-default': active,
|
||||
'text-muted': !active,
|
||||
})}
|
||||
data-testid={`key-${pk.publicKey}`}
|
||||
>
|
||||
<span className={classNames('mr-2 uppercase')}>
|
||||
{pk.name}
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{pk.name ? pk.name : t('Unnamed key')}</span>
|
||||
{' | '}
|
||||
{truncateByChars(pk.publicKey)}
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<span className="font-mono">
|
||||
{truncateByChars(pk.publicKey, 3, 3)}
|
||||
</span>
|
||||
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
|
||||
<button
|
||||
data-testid="copy-vega-public-key"
|
||||
className="relative -top-px"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span className="sr-only">{t('Copy')}</span>
|
||||
@@ -170,7 +188,17 @@ const KeypairItem = ({ pk, active }: { pk: Key; active: boolean }) => {
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
{copied && <span className="text-xs">{t('Copied')}</span>}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={classNames('flex-1 mr-2 text-secondary text-sm')}
|
||||
data-testid={`key-${pk.publicKey}`}
|
||||
>
|
||||
<Tooltip description={t('Public facing key alias. Click to edit')}>
|
||||
<button data-testid="alias" onClick={() => setOpen(pk.publicKey)}>
|
||||
{alias ? alias : t('No alias')}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownRadioItem>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
|
||||
VEGA_VERSION=v0.74.6
|
||||
VEGA_VERSION=v0.75.0-preview.2
|
||||
LOCAL_SERVER=false
|
||||
|
||||
@@ -50,8 +50,6 @@ def truncate_middle(market_id, start=6, end=4):
|
||||
def change_keys(page: Page, vega: VegaServiceNull, key_name):
|
||||
page.get_by_test_id("manage-vega-wallet").click()
|
||||
page.get_by_test_id("key-" + vega.wallet.public_key(key_name)).click()
|
||||
page.click(
|
||||
f'data-testid=key-{vega.wallet.public_key(key_name)} >> .inline-flex')
|
||||
page.reload()
|
||||
|
||||
|
||||
|
||||
Generated
+4
-4
@@ -877,13 +877,13 @@ testing = ["filelock"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.8.2"
|
||||
version = "2.9.0.post0"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
files = [
|
||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
|
||||
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1166,7 +1166,7 @@ profile = ["pytest-profiling", "snakeviz"]
|
||||
type = "git"
|
||||
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
|
||||
reference = "HEAD"
|
||||
resolved_reference = "33fec45ce8044ef7f53b625584ce590d174f9057"
|
||||
resolved_reference = "53eed8942acb670783105cb1115bab76710a46dc"
|
||||
|
||||
[[package]]
|
||||
name = "websocket-client"
|
||||
|
||||
@@ -77,7 +77,7 @@ def test_market_info_market_volume(page: Page):
|
||||
page.get_by_test_id(market_title_test_id).get_by_text(
|
||||
"Market volume").click()
|
||||
fields = [
|
||||
["24 Hour Volume", "-"],
|
||||
["24 Hour Volume", "0 (0 )"],
|
||||
["Open Interest", "1"],
|
||||
["Best Bid Volume", "99"],
|
||||
["Best Offer Volume", "99"],
|
||||
|
||||
@@ -57,7 +57,7 @@ class TestPerpetuals:
|
||||
page.goto(f"/#/markets/{perps_market}")
|
||||
page.get_by_test_id("Funding payments").click()
|
||||
row = page.locator(row_selector)
|
||||
expect(row.locator(col_amount)).to_have_text("9.00 tDAI")
|
||||
expect(row.locator(col_amount)).to_have_text("4.45 tDAI")
|
||||
|
||||
@pytest.mark.usefixtures("risk_accepted", "auth")
|
||||
def test_funding_payment_loss(self, perps_market, page: Page, vega):
|
||||
@@ -65,7 +65,7 @@ class TestPerpetuals:
|
||||
change_keys(page, vega, "market_maker")
|
||||
page.get_by_test_id("Funding payments").click()
|
||||
row = page.locator(row_selector)
|
||||
expect(row.locator(col_amount)).to_have_text("-27.00 tDAI")
|
||||
expect(row.locator(col_amount)).to_have_text("-13.35 tDAI")
|
||||
|
||||
@pytest.mark.usefixtures("risk_accepted", "auth")
|
||||
def test_funding_header(self, perps_market, page: Page):
|
||||
|
||||
@@ -25,8 +25,12 @@ fragment GameFields on Game {
|
||||
}
|
||||
}
|
||||
|
||||
query Games($epochFrom: Int) {
|
||||
games(epochFrom: $epochFrom, entityScope: ENTITY_SCOPE_TEAMS) {
|
||||
query Games($epochFrom: Int, $teamId: ID) {
|
||||
games(
|
||||
epochFrom: $epochFrom
|
||||
teamId: $teamId
|
||||
entityScope: ENTITY_SCOPE_TEAMS
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...GameFields
|
||||
|
||||
+4
-2
@@ -9,6 +9,7 @@ export type GameFieldsFragment = { __typename?: 'Game', id: string, epoch: numbe
|
||||
|
||||
export type GamesQueryVariables = Types.Exact<{
|
||||
epochFrom?: Types.InputMaybe<Types.Scalars['Int']>;
|
||||
teamId?: Types.InputMaybe<Types.Scalars['ID']>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -44,8 +45,8 @@ export const GameFieldsFragmentDoc = gql`
|
||||
}
|
||||
${TeamEntityFragmentDoc}`;
|
||||
export const GamesDocument = gql`
|
||||
query Games($epochFrom: Int) {
|
||||
games(epochFrom: $epochFrom, entityScope: ENTITY_SCOPE_TEAMS) {
|
||||
query Games($epochFrom: Int, $teamId: ID) {
|
||||
games(epochFrom: $epochFrom, teamId: $teamId, entityScope: ENTITY_SCOPE_TEAMS) {
|
||||
edges {
|
||||
node {
|
||||
...GameFields
|
||||
@@ -68,6 +69,7 @@ export const GamesDocument = gql`
|
||||
* const { data, loading, error } = useGamesQuery({
|
||||
* variables: {
|
||||
* epochFrom: // value for 'epochFrom'
|
||||
* teamId: // value for 'teamId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -51,6 +51,7 @@ export const useGames = (teamId?: string, epochFrom?: number): GamesData => {
|
||||
const { data, loading, error } = useGamesQuery({
|
||||
variables: {
|
||||
epochFrom: from,
|
||||
teamId: teamId,
|
||||
},
|
||||
skip: !from,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
ProtocolUpgradeInProgressNotification,
|
||||
ProtocolUpgradeProposalNotification,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { Telemetry } from '../components/telemetry';
|
||||
import { SSRLoader } from './ssr-loader';
|
||||
import { PartyActiveOrdersHandler } from './party-active-orders-handler';
|
||||
@@ -77,7 +76,6 @@ function AppBody({ Component }: AppProps) {
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
<ProtocolUpgradeInProgressNotification />
|
||||
<ViewingBanner />
|
||||
</div>
|
||||
<div data-testid={`pathname-${location.pathname}`}>
|
||||
<Component />
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@vegaprotocol/web3';
|
||||
import { WelcomeDialog } from '../components/welcome-dialog';
|
||||
import { VegaWalletConnectDialog } from '../components/vega-wallet-connect-dialog';
|
||||
import { ProfileDialog } from '../components/profile-dialog';
|
||||
|
||||
const DialogsContainer = () => {
|
||||
const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore();
|
||||
@@ -24,6 +25,7 @@ const DialogsContainer = () => {
|
||||
<WelcomeDialog />
|
||||
<Web3ConnectUncontrolledDialog />
|
||||
<WithdrawalApprovalDialogContainer />
|
||||
<ProfileDialog />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface ProfileDialogStore {
|
||||
open: boolean;
|
||||
pubKey: string | undefined;
|
||||
setOpen: (pubKey: string | undefined) => void;
|
||||
}
|
||||
|
||||
export const useProfileDialogStore = create<ProfileDialogStore>((set) => ({
|
||||
open: false,
|
||||
pubKey: undefined,
|
||||
setOpen: (pubKey) => {
|
||||
if (pubKey) {
|
||||
set({ open: true, pubKey });
|
||||
} else {
|
||||
set({ open: false, pubKey: undefined });
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -12,7 +12,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const chainId = 'test-id';
|
||||
const chainId = 'vega-stagnet1-202307191148';
|
||||
|
||||
export function addMockChainId() {
|
||||
Cypress.Commands.add('mockChainId', () => {
|
||||
|
||||
@@ -37,10 +37,12 @@ export function addVegaWalletConnect() {
|
||||
Cypress.Commands.add('connectVegaWallet', (isMobile) => {
|
||||
mockConnectWallet();
|
||||
cy.highlight(`Connecting Vega Wallet`);
|
||||
cy.get('[data-testid="splash-loader"]', { timeout: 20000 }).should(
|
||||
'not.exist'
|
||||
);
|
||||
const connectVegaWalletButton = `[data-testid=connect-vega-wallet${
|
||||
isMobile ? '-mobile' : ''
|
||||
}]:visible`;
|
||||
|
||||
cy.get(connectVegaWalletButton).then((btn) => {
|
||||
if (btn.length === 0) {
|
||||
cy.log('could not find the button, perhaps already connected');
|
||||
|
||||
@@ -13,7 +13,8 @@ declare global {
|
||||
|
||||
const hasMethod = (req: CyHttpMessages.IncomingHttpRequest, method: string) => {
|
||||
const { body } = req;
|
||||
return 'method' in body && body.method === method;
|
||||
const b = JSON.parse(body);
|
||||
return 'method' in b && b.method === method;
|
||||
};
|
||||
|
||||
export function addMockWalletCommand() {
|
||||
@@ -72,7 +73,7 @@ export const aliasWalletConnectQuery = (
|
||||
body: {
|
||||
jsonrpc: '2.0',
|
||||
result: {
|
||||
chainID: 'test-id',
|
||||
chainID: 'vega-fairground-202305051805',
|
||||
},
|
||||
id: '1',
|
||||
},
|
||||
|
||||
@@ -134,6 +134,7 @@ export const CONSOLE_TRANSFER = '#/portfolio/assets/transfer';
|
||||
export const CONSOLE_TRANSFER_ASSET =
|
||||
'#/portfolio/assets/transfer?assetId=:assetId';
|
||||
export const CONSOLE_MARKET_PAGE = '#/markets/:marketId';
|
||||
export const CONSOLE_REWARDS_PAGE = '#/rewards';
|
||||
|
||||
// Governance pages
|
||||
export const TOKEN_NEW_MARKET_PROPOSAL = '/proposals/propose/new-market';
|
||||
|
||||
@@ -971,5 +971,6 @@
|
||||
"YourIdentityAnonymous": "Your identity is always anonymous on Vega",
|
||||
"yourStake": "Your stake",
|
||||
"yourVote": "Your vote",
|
||||
"youVoted": "You voted"
|
||||
"youVoted": "You voted",
|
||||
"rewardsMovedNotification": "Trading and liquidity rewards have moved. Visit <0>Console</0> to view your rewards."
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"{{liquidityPriceRange}} of mid price": "{{liquidityPriceRange}} of mid price",
|
||||
"{{probability}} probability price bounds": "{{probability}} probability price bounds",
|
||||
"24 hour change is unavailable at this time. The price change in the last 120 hours is:": "24 hour change is unavailable at this time. The price change in the last 120 hours is:",
|
||||
"24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}} for a total of {{candleVolumePrice}} {{quoteUnit}}": "24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}} for a total of {{candleVolumePrice}} {{quoteUnit}}",
|
||||
"A concept derived from traditional markets. It is a calculated value for the ‘current market price’ on a market.": "A concept derived from traditional markets. It is a calculated value for the ‘current market price’ on a market.",
|
||||
"A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.": "A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.",
|
||||
"A sliding penalty for how much an LP bond is slashed if an LP fails to reach the minimum SLA. This is a network parameter.": "A sliding penalty for how much an LP bond is slashed if an LP fails to reach the minimum SLA. This is a network parameter.",
|
||||
@@ -51,9 +49,6 @@
|
||||
"Market": "Market",
|
||||
"Market data": "Market data",
|
||||
"Market governance": "Market governance",
|
||||
"Market has not been active for 24 hours. The price change between {{start}} and {{end}} is:": "Market has not been active for 24 hours. The price change between {{start}} and {{end}} is:",
|
||||
"Market has not been active for 24 hours. The volume traded between {{start}} and {{end}} is:": "Market has not been active for 24 hours. The volume traded between {{start}} and {{end}} is:",
|
||||
"Market has not been active for 24 hours. The volume traded between {{start}} and {{end}} is {{candleVolumeValue}} for a total of {{candleVolumePrice}} {{quoteUnit}}": "Market has not been active for 24 hours. The volume traded between {{start}} and {{end}} is {{candleVolumeValue}} for a total of {{candleVolumePrice}} {{quoteUnit}}",
|
||||
"Market ID": "Market ID",
|
||||
"Market price": "Market price",
|
||||
"Market specification": "Market specification",
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
"Docs": "Docs",
|
||||
"Earn commission & stake rewards": "Earn commission & stake rewards",
|
||||
"Earned by me": "Earned by me",
|
||||
"Edit alias": "Edit alias",
|
||||
"Eligible teams": "Eligible teams",
|
||||
"Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass",
|
||||
"[empty]": "[empty]",
|
||||
@@ -162,6 +163,7 @@
|
||||
"Joined": "Joined",
|
||||
"Joined at": "Joined at",
|
||||
"Joined epoch": "Joined epoch",
|
||||
"Key name": "Key name",
|
||||
"gameCount_one": "Last game result",
|
||||
"gameCount_other": "Last {{count}} game results",
|
||||
"Learn about providing liquidity": "Learn about providing liquidity",
|
||||
@@ -194,6 +196,7 @@
|
||||
"My liquidity provision": "My liquidity provision",
|
||||
"My trading fees": "My trading fees",
|
||||
"Name": "Name",
|
||||
"No alias": "No alias",
|
||||
"No closed orders": "No closed orders",
|
||||
"No data": "No data",
|
||||
"No deposits": "No deposits",
|
||||
@@ -227,6 +230,7 @@
|
||||
"Not connected": "Not connected",
|
||||
"Number of epochs after distribution to delay vesting of rewards by": "Number of epochs after distribution to delay vesting of rewards by",
|
||||
"Number of traders": "Number of traders",
|
||||
"On-change alias": "On-change alias",
|
||||
"Open": "Open",
|
||||
"Open a position": "Open a position",
|
||||
"Open markets": "Open markets",
|
||||
@@ -249,6 +253,7 @@
|
||||
"Portfolio": "Portfolio",
|
||||
"Positions": "Positions",
|
||||
"Price": "Price",
|
||||
"Profile updated": "Profile updated",
|
||||
"Program ends:": "Program ends:",
|
||||
"Propose a new market": "Propose a new market",
|
||||
"Proposed final price is {{price}} {{assetSymbol}}.": "Proposed final price is {{price}} {{assetSymbol}}.",
|
||||
@@ -289,6 +294,7 @@
|
||||
"Search": "Search",
|
||||
"See all markets": "See all markets",
|
||||
"Select market": "Select market",
|
||||
"Set party alias": "Set party alias",
|
||||
"Settings": "Settings",
|
||||
"Settlement asset": "Settlement asset",
|
||||
"Settlement date": "Settlement date",
|
||||
@@ -311,6 +317,7 @@
|
||||
"Stop": "Stop",
|
||||
"Stop orders": "Stop orders",
|
||||
"Streak reward multiplier": "Streak reward multiplier",
|
||||
"Submit": "Submit",
|
||||
"Successor of a market": "Successor of a market",
|
||||
"Successors to this market have been proposed": "Successors to this market have been proposed",
|
||||
"Supplied stake": "Supplied stake",
|
||||
@@ -371,6 +378,7 @@
|
||||
"Staking rewards": "Staking rewards",
|
||||
"Unknown": "Unknown",
|
||||
"Unknown settlement date": "Unknown settlement date",
|
||||
"Unnamed key": "Unnamed key",
|
||||
"Update team": "Update team",
|
||||
"URL": "URL",
|
||||
"Use a comma separated list to allow only specific public keys to join the team": "Use a comma separated list to allow only specific public keys to join the team",
|
||||
@@ -407,8 +415,10 @@
|
||||
"You will no longer be able to hold a position on this market when it closes in {{duration}}.": "You will no longer be able to hold a position on this market when it closes in {{duration}}.",
|
||||
"Your code has been rejected": "Your code has been rejected",
|
||||
"Your identity is always anonymous on Vega": "Your identity is always anonymous on Vega",
|
||||
"Your key's private name, can be changed in your wallet": "Your key's private name, can be changed in your wallet",
|
||||
"Your referral code": "Your referral code",
|
||||
"Your tier": "Your tier",
|
||||
"Your public alias, stored on chain": "Your public alias, stored on chain",
|
||||
"checkOutProposalsAndVote": "Check out the terms of the proposals and vote:",
|
||||
"checkOutProposalsAndVote_one": "Check out the terms of the proposal and vote:",
|
||||
"checkOutProposalsAndVote_other": "Check out the terms of the proposals and vote:",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"Get MetaMask": "Get MetaMask",
|
||||
"Get the Vega Wallet": "Get the Vega Wallet",
|
||||
"I agree": "I agree",
|
||||
"Once you have the added the extension, <0>refresh</0> you browser.": "Once you have the added the extension, <0>refresh</0> you browser.",
|
||||
"Successfully connected": "Successfully connected",
|
||||
"Transaction was not successful": "Transaction was not successful",
|
||||
"Wallet rejected transaction": "Wallet rejected transaction"
|
||||
|
||||
@@ -2,16 +2,14 @@ import { type ReactNode } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumberPercentage,
|
||||
getDateTimeFormat,
|
||||
priceChange,
|
||||
priceChangePercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { PriceChangeCell, signedNumberCssClass } from '@vegaprotocol/datagrid';
|
||||
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { signedNumberCssClass } from '@vegaprotocol/datagrid';
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { useCandles } from '../../hooks/use-candles';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import classNames from 'classnames';
|
||||
import { useT } from '../../use-t';
|
||||
|
||||
interface Props {
|
||||
marketId?: string;
|
||||
@@ -24,7 +22,6 @@ export const Last24hPriceChange = ({
|
||||
decimalPlaces,
|
||||
fallback,
|
||||
}: Props) => {
|
||||
const t = useT();
|
||||
const { oneDayCandles, fiveDaysCandles, error } = useCandles({
|
||||
marketId,
|
||||
});
|
||||
@@ -35,56 +32,6 @@ export const Last24hPriceChange = ({
|
||||
return nonIdeal;
|
||||
}
|
||||
|
||||
if (fiveDaysCandles.length < 24) {
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<span className="justify-start">
|
||||
{t(
|
||||
'Market has not been active for 24 hours. The price change between {{start}} and {{end}} is:',
|
||||
{
|
||||
start: getDateTimeFormat().format(
|
||||
new Date(fiveDaysCandles[0].periodStart)
|
||||
),
|
||||
end: getDateTimeFormat().format(
|
||||
new Date(
|
||||
fiveDaysCandles[fiveDaysCandles.length - 1].periodStart
|
||||
)
|
||||
),
|
||||
}
|
||||
)}
|
||||
<PriceChangeCell
|
||||
candles={fiveDaysCandles.map((c) => c.close) || []}
|
||||
decimalPlaces={decimalPlaces}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<span>{nonIdeal}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (oneDayCandles.length < 24) {
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<span className="justify-start">
|
||||
{t(
|
||||
'24 hour change is unavailable at this time. The price change in the last 120 hours is:'
|
||||
)}{' '}
|
||||
<PriceChangeCell
|
||||
candles={fiveDaysCandles.map((c) => c.close) || []}
|
||||
decimalPlaces={decimalPlaces}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<span>{nonIdeal}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
const candles = oneDayCandles?.map((c) => c.close) || [];
|
||||
const change = priceChange(candles);
|
||||
const changePercentage = priceChangePercentage(candles);
|
||||
|
||||
@@ -2,7 +2,6 @@ import { calcCandleVolume, calcCandleVolumePrice } from '../../market-utils';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumber,
|
||||
getDateTimeFormat,
|
||||
isNumeric,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
@@ -37,83 +36,6 @@ export const Last24hVolume = ({
|
||||
return nonIdeal;
|
||||
}
|
||||
|
||||
if (fiveDaysCandles.length < 24) {
|
||||
const candleVolume = calcCandleVolume(fiveDaysCandles);
|
||||
const candleVolumePrice = calcCandleVolumePrice(
|
||||
fiveDaysCandles,
|
||||
marketDecimals,
|
||||
positionDecimalPlaces
|
||||
);
|
||||
const candleVolumeValue =
|
||||
candleVolume && isNumeric(positionDecimalPlaces)
|
||||
? addDecimalsFormatNumber(
|
||||
candleVolume,
|
||||
positionDecimalPlaces,
|
||||
formatDecimals
|
||||
)
|
||||
: '-';
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<div>
|
||||
<span className="flex flex-col">
|
||||
{t(
|
||||
'Market has not been active for 24 hours. The volume traded between {{start}} and {{end}} is {{candleVolumeValue}} for a total of {{candleVolumePrice}} {{quoteUnit}}',
|
||||
{
|
||||
start: getDateTimeFormat().format(
|
||||
new Date(fiveDaysCandles[0].periodStart)
|
||||
),
|
||||
end: getDateTimeFormat().format(
|
||||
new Date(
|
||||
fiveDaysCandles[fiveDaysCandles.length - 1].periodStart
|
||||
)
|
||||
),
|
||||
candleVolumeValue,
|
||||
candleVolumePrice,
|
||||
quoteUnit,
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<span>{nonIdeal}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (oneDayCandles.length < 24) {
|
||||
const candleVolume = calcCandleVolume(fiveDaysCandles);
|
||||
const candleVolumePrice = calcCandleVolumePrice(
|
||||
fiveDaysCandles,
|
||||
marketDecimals,
|
||||
positionDecimalPlaces
|
||||
);
|
||||
const candleVolumeValue =
|
||||
candleVolume && isNumeric(positionDecimalPlaces)
|
||||
? addDecimalsFormatNumber(
|
||||
candleVolume,
|
||||
positionDecimalPlaces,
|
||||
formatDecimals
|
||||
)
|
||||
: '-';
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<div>
|
||||
<span className="flex flex-col">
|
||||
{t(
|
||||
'24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}} for a total of ({{candleVolumePrice}} {{quoteUnit}})',
|
||||
{ candleVolumeValue, candleVolumePrice, quoteUnit }
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<span>{nonIdeal}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
const candleVolume = oneDayCandles
|
||||
? calcCandleVolume(oneDayCandles)
|
||||
: initialValue;
|
||||
|
||||
@@ -8,7 +8,7 @@ export const useCandles = ({ marketId }: { marketId?: string }) => {
|
||||
const fiveDaysAgo = useFiveDaysAgo();
|
||||
const yesterday = useYesterday();
|
||||
const since = new Date(fiveDaysAgo).toISOString();
|
||||
const { data, error } = useThrottledDataProvider({
|
||||
const { data: fiveDaysCandles, error } = useThrottledDataProvider({
|
||||
dataProvider: marketCandlesProvider,
|
||||
variables: {
|
||||
marketId: marketId || '',
|
||||
@@ -18,13 +18,6 @@ export const useCandles = ({ marketId }: { marketId?: string }) => {
|
||||
skip: !marketId,
|
||||
});
|
||||
|
||||
const fiveDaysCandles = data?.filter((c) => {
|
||||
if (c.open === '' || c.close === '' || c.high === '' || c.close === '') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const oneDayCandles = fiveDaysCandles?.filter((candle) =>
|
||||
isCandleLessThan24hOld(candle, yesterday)
|
||||
);
|
||||
|
||||
Generated
+12
@@ -936,6 +936,8 @@ export enum DispatchMetric {
|
||||
/** Dispatch strategy for a recurring transfer */
|
||||
export type DispatchStrategy = {
|
||||
__typename?: 'DispatchStrategy';
|
||||
/** Optional multiplier on taker fees used to cap the rewards a party may receive in an epoch */
|
||||
capRewardFeeMultiple?: Maybe<Scalars['String']>;
|
||||
/** Defines the data that will be used to compare markets so as to distribute rewards appropriately */
|
||||
dispatchMetric: DispatchMetric;
|
||||
/** The asset to use for measuring contribution to the metric */
|
||||
@@ -2391,6 +2393,8 @@ export type Market = {
|
||||
state: MarketState;
|
||||
/** Optional: Market ID of the successor to this market if one exists */
|
||||
successorMarketID?: Maybe<Scalars['ID']>;
|
||||
/** The market minimum tick size */
|
||||
tickSize: Scalars['String'];
|
||||
/** An instance of, or reference to, a tradable instrument. */
|
||||
tradableInstrument: TradableInstrument;
|
||||
/** @deprecated Simplify and consolidate trades query and remove nesting. Use trades query instead */
|
||||
@@ -2816,6 +2820,8 @@ export type NewMarket = {
|
||||
riskParameters: RiskModel;
|
||||
/** Successor market configuration. If this proposed market is meant to succeed a given market, then this needs to be set. */
|
||||
successorConfiguration?: Maybe<SuccessorConfiguration>;
|
||||
/** The market minimum tick size */
|
||||
tickSize: Scalars['String'];
|
||||
};
|
||||
|
||||
/** Configuration for a new spot market on Vega */
|
||||
@@ -2839,6 +2845,8 @@ export type NewSpotMarket = {
|
||||
riskParameters?: Maybe<RiskModel>;
|
||||
/** Specifies parameters related to liquidity target stake calculation */
|
||||
targetStakeParameters: TargetStakeParameters;
|
||||
/** The market minimum tick size */
|
||||
tickSize: Scalars['String'];
|
||||
};
|
||||
|
||||
export type NewTransfer = {
|
||||
@@ -7074,6 +7082,8 @@ export type UpdateMarketConfiguration = {
|
||||
quadraticSlippageFactor: Scalars['String'];
|
||||
/** Updated futures market risk model parameters. */
|
||||
riskParameters: UpdateMarketRiskParameters;
|
||||
/** The market minimum tick size */
|
||||
tickSize: Scalars['String'];
|
||||
};
|
||||
|
||||
export type UpdateMarketLogNormalRiskModel = {
|
||||
@@ -7171,6 +7181,8 @@ export type UpdateSpotMarketConfiguration = {
|
||||
riskParameters: RiskModel;
|
||||
/** Specifies parameters related to target stake calculation */
|
||||
targetStakeParameters: TargetStakeParameters;
|
||||
/** The market minimum tick size */
|
||||
tickSize: Scalars['String'];
|
||||
};
|
||||
|
||||
export type UpdateVolumeDiscountProgram = {
|
||||
|
||||
@@ -17,8 +17,8 @@ export const InputError = ({
|
||||
...props
|
||||
}: InputErrorProps) => {
|
||||
const effectiveClassName = classNames(
|
||||
'text-sm flex items-center first-letter:uppercase',
|
||||
'mt-2',
|
||||
'text-sm block items-center first-letter:capitalize',
|
||||
'mt-2 min-w-0 break-words',
|
||||
{
|
||||
'border-danger': intent === 'danger',
|
||||
'border-warning': intent === 'warning',
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { type ReactNode, type FunctionComponent, forwardRef } from 'react';
|
||||
import {
|
||||
type ReactNode,
|
||||
type FunctionComponent,
|
||||
forwardRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
ConnectorErrors,
|
||||
isBrowserWalletInstalled,
|
||||
@@ -12,6 +17,7 @@ import { useConnect } from '../../hooks/use-connect';
|
||||
import { Links } from '../../constants';
|
||||
import { ConnectorIcon } from './connector-icon';
|
||||
import { useUserAgent } from '@vegaprotocol/react-helpers';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
const vegaExtensionsLinks = {
|
||||
chrome: Links.chromeExtension,
|
||||
@@ -29,48 +35,67 @@ export const ConnectionOptions = ({
|
||||
onConnect: (id: ConnectorType) => void;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const error = useWallet((store) => store.error);
|
||||
const { connectors } = useConnect();
|
||||
const error = useWallet((store) => store.error);
|
||||
const [isInstalling, setIsInstalling] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<h2 className="text-xl">{t('Connect to Vega')}</h2>
|
||||
<ul
|
||||
className="grid grid-cols-1 sm:grid-cols-2 gap-1 -mx-2"
|
||||
data-testid="connectors-list"
|
||||
>
|
||||
{connectors.map((c) => {
|
||||
const ConnectionOption = ConnectionOptionRecord[c.id];
|
||||
const props = {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
description: c.description,
|
||||
showDescription: false,
|
||||
onClick: () => onConnect(c.id),
|
||||
};
|
||||
|
||||
if (ConnectionOption) {
|
||||
return (
|
||||
<li key={c.id}>
|
||||
<ConnectionOption {...props} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={c.id}>
|
||||
<ConnectionOptionDefault {...props} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{error && error.code !== ConnectorErrors.userRejected.code && (
|
||||
<p
|
||||
className="text-danger text-sm first-letter:uppercase"
|
||||
data-testid="connection-error"
|
||||
>
|
||||
{error.message}
|
||||
{isInstalling ? (
|
||||
<p className="text-warning">
|
||||
<Trans
|
||||
i18nKey="Once you have the added the extension, <0>refresh</0> you browser."
|
||||
components={[
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="underline underline-offset-4"
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<ul
|
||||
className="grid grid-cols-1 sm:grid-cols-2 gap-1 -mx-2"
|
||||
data-testid="connectors-list"
|
||||
>
|
||||
{connectors.map((c) => {
|
||||
const ConnectionOption = ConnectionOptionRecord[c.id];
|
||||
const props = {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
description: c.description,
|
||||
showDescription: false,
|
||||
onClick: () => onConnect(c.id),
|
||||
onInstall: () => setIsInstalling(true),
|
||||
};
|
||||
|
||||
if (ConnectionOption) {
|
||||
return (
|
||||
<li key={c.id}>
|
||||
<ConnectionOption {...props} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={c.id}>
|
||||
<ConnectionOptionDefault {...props} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{error && error.code !== ConnectorErrors.userRejected.code && (
|
||||
<p
|
||||
className="text-danger text-sm first-letter:uppercase"
|
||||
data-testid="connection-error"
|
||||
>
|
||||
{error.message}
|
||||
{error.data ? `: ${error.data}` : ''}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<a
|
||||
href={Links.walletOverview}
|
||||
@@ -90,6 +115,7 @@ interface ConnectionOptionProps {
|
||||
description: string;
|
||||
showDescription?: boolean;
|
||||
onClick: () => void;
|
||||
onInstall?: () => void;
|
||||
}
|
||||
|
||||
const CONNECTION_OPTION_CLASSES =
|
||||
@@ -142,6 +168,7 @@ export const ConnectionOptionInjected = ({
|
||||
description,
|
||||
showDescription = false,
|
||||
onClick,
|
||||
onInstall,
|
||||
}: ConnectionOptionProps) => {
|
||||
const t = useT();
|
||||
const userAgent = useUserAgent();
|
||||
@@ -158,7 +185,11 @@ export const ConnectionOptionInjected = ({
|
||||
</span>
|
||||
</ConnectionOptionButtonWithDescription>
|
||||
) : (
|
||||
<ConnectionOptionLinkWithDescription id={id} href={link}>
|
||||
<ConnectionOptionLinkWithDescription
|
||||
id={id}
|
||||
href={link}
|
||||
onClick={onInstall}
|
||||
>
|
||||
<span className="flex flex-col justify-start text-left">
|
||||
<span className="capitalize leading-5">
|
||||
{t('Get the Vega Wallet')}
|
||||
@@ -183,7 +214,7 @@ export const ConnectionOptionInjected = ({
|
||||
{name}
|
||||
</ConnectionOptionButton>
|
||||
) : (
|
||||
<ConnectionOptionLink id={id} href={link}>
|
||||
<ConnectionOptionLink id={id} href={link} onClick={onInstall}>
|
||||
{t('Get the Vega Wallet')}
|
||||
</ConnectionOptionLink>
|
||||
)}
|
||||
@@ -275,8 +306,9 @@ const ConnectionOptionLink = forwardRef<
|
||||
children: ReactNode;
|
||||
id: ConnectorType;
|
||||
href: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
>(({ children, id, href }, ref) => {
|
||||
>(({ children, id, href, onClick }, ref) => {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
@@ -285,6 +317,7 @@ const ConnectionOptionLink = forwardRef<
|
||||
className={CONNECTION_OPTION_CLASSES}
|
||||
data-testid={`connector-${id}`}
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
>
|
||||
<ConnectorIcon id={id} />
|
||||
{children}
|
||||
@@ -320,8 +353,10 @@ const ConnectionOptionLinkWithDescription = forwardRef<
|
||||
children: ReactNode;
|
||||
id: ConnectorType;
|
||||
href: string;
|
||||
|
||||
onClick?: () => void;
|
||||
}
|
||||
>(({ children, id, href }, ref) => {
|
||||
>(({ children, id, href, onClick }, ref) => {
|
||||
return (
|
||||
<a
|
||||
ref={ref}
|
||||
@@ -329,6 +364,7 @@ const ConnectionOptionLinkWithDescription = forwardRef<
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={onClick}
|
||||
>
|
||||
<span>
|
||||
<ConnectorIcon id={id} />
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useWallet } from './use-wallet';
|
||||
import { useConnect } from './use-connect';
|
||||
|
||||
export function useEagerConnect() {
|
||||
const current = useWallet((store) => store.current);
|
||||
const status = useWallet((store) => store.status);
|
||||
const { connect } = useConnect();
|
||||
const [connecting, setConnecting] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const attemptConnect = async () => {
|
||||
// No stored config, or config was malformed or no risk accepted
|
||||
if (!current) {
|
||||
setConnecting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status !== 'disconnected') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,15 +22,13 @@ export function useEagerConnect() {
|
||||
await connect(current);
|
||||
} catch {
|
||||
console.warn(`Failed to connect with connector: ${current}`);
|
||||
} finally {
|
||||
setConnecting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
attemptConnect();
|
||||
}
|
||||
}, [connect, current, connecting]);
|
||||
}, [status, connect, current]);
|
||||
|
||||
return connecting;
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@ export const useSimpleTransaction = (opts?: Options) => {
|
||||
const [result, setResult] = useState<Result>();
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const reset = () => {
|
||||
setStatus('idle');
|
||||
setResult(undefined);
|
||||
setError(undefined);
|
||||
};
|
||||
|
||||
const send = async (tx: Transaction) => {
|
||||
if (!pubKey) {
|
||||
throw new Error('no pubKey');
|
||||
@@ -59,12 +65,12 @@ export const useSimpleTransaction = (opts?: Options) => {
|
||||
if (err.code === ConnectorErrors.userRejected.code) {
|
||||
setStatus('idle');
|
||||
} else {
|
||||
setError(err.message);
|
||||
setError(`${err.message}${err.data ? `: ${err.data}` : ''}`);
|
||||
setStatus('idle');
|
||||
opts?.onError?.(err.message);
|
||||
}
|
||||
} else {
|
||||
const msg = t('Wallet rejected transaction');
|
||||
const msg = t('Something went wrong');
|
||||
setError(msg);
|
||||
setStatus('idle');
|
||||
opts?.onError?.(msg);
|
||||
@@ -114,5 +120,6 @@ export const useSimpleTransaction = (opts?: Options) => {
|
||||
error,
|
||||
status,
|
||||
send,
|
||||
reset,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
listKeysError,
|
||||
noWalletError,
|
||||
sendTransactionError,
|
||||
userRejectedError,
|
||||
} from '../errors';
|
||||
import {
|
||||
type TransactionParams,
|
||||
@@ -14,6 +15,19 @@ import {
|
||||
type VegaWalletEvent,
|
||||
} from '../types';
|
||||
|
||||
interface InjectedError {
|
||||
message: string;
|
||||
code: number;
|
||||
data:
|
||||
| {
|
||||
message: string;
|
||||
code: number;
|
||||
}
|
||||
| string;
|
||||
}
|
||||
|
||||
const USER_REJECTED_CODE = -4;
|
||||
|
||||
export class InjectedConnector implements Connector {
|
||||
readonly id = 'injected';
|
||||
readonly name = 'Vega Wallet';
|
||||
@@ -85,15 +99,55 @@ export class InjectedConnector implements Connector {
|
||||
sentAt: res.sentAt,
|
||||
};
|
||||
} catch (err) {
|
||||
if (this.isInjectedError(err)) {
|
||||
if (err.code === USER_REJECTED_CODE) {
|
||||
throw userRejectedError();
|
||||
}
|
||||
|
||||
if (typeof err.data === 'string') {
|
||||
throw sendTransactionError(err.data);
|
||||
} else {
|
||||
throw sendTransactionError(err.data.message);
|
||||
}
|
||||
}
|
||||
|
||||
throw sendTransactionError();
|
||||
}
|
||||
}
|
||||
|
||||
on(event: VegaWalletEvent, callback: () => void) {
|
||||
window.vega.on(event, callback);
|
||||
// Check for on/off in case user is on older versions which don't support it
|
||||
// We can remove this check once FF is at the latest version
|
||||
if (
|
||||
typeof window.vega !== 'undefined' &&
|
||||
typeof window.vega.on === 'function'
|
||||
) {
|
||||
window.vega.on(event, callback);
|
||||
}
|
||||
}
|
||||
|
||||
off(event: VegaWalletEvent, callback: () => void) {
|
||||
window.vega.off(event, callback);
|
||||
// Check for on/off in case user is on older versions which don't support it
|
||||
// We can remove this check once FF is at the latest version
|
||||
if (
|
||||
typeof window.vega !== 'undefined' &&
|
||||
typeof window.vega.off === 'function'
|
||||
) {
|
||||
window.vega.off(event, callback);
|
||||
}
|
||||
}
|
||||
|
||||
private isInjectedError(obj: unknown): obj is InjectedError {
|
||||
if (
|
||||
obj !== undefined &&
|
||||
obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
'code' in obj &&
|
||||
'message' in obj &&
|
||||
'data' in obj
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
|
||||
type JsonRpcConnectorConfig = { url: string; token?: string };
|
||||
|
||||
const USER_REJECTED_CODE = 3001;
|
||||
|
||||
export class JsonRpcConnector implements Connector {
|
||||
readonly id = 'jsonRpc';
|
||||
readonly name = 'Command Line Wallet';
|
||||
@@ -27,7 +29,7 @@ export class JsonRpcConnector implements Connector {
|
||||
requestId: number = 0;
|
||||
store: StoreApi<Store> | undefined;
|
||||
pollRef: NodeJS.Timer | undefined;
|
||||
ee: EventEmitter;
|
||||
ee: InstanceType<typeof EventEmitter>;
|
||||
|
||||
constructor(config: JsonRpcConnectorConfig) {
|
||||
this.url = config.url;
|
||||
@@ -63,7 +65,7 @@ export class JsonRpcConnector implements Connector {
|
||||
const token = response.headers.get('Authorization');
|
||||
|
||||
if (!response.ok) {
|
||||
if ('error' in data && data.error.code === 3001) {
|
||||
if ('error' in data && data.error.code === USER_REJECTED_CODE) {
|
||||
throw userRejectedError();
|
||||
}
|
||||
throw connectError('response not ok');
|
||||
@@ -137,7 +139,7 @@ export class JsonRpcConnector implements Connector {
|
||||
|
||||
if (!response.ok) {
|
||||
if ('error' in data) {
|
||||
if (data.error.code === 3001) {
|
||||
if (data.error.code === USER_REJECTED_CODE) {
|
||||
throw userRejectedError();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import {
|
||||
ConnectorError,
|
||||
chainIdError,
|
||||
@@ -6,13 +5,13 @@ import {
|
||||
listKeysError,
|
||||
noWalletError,
|
||||
sendTransactionError,
|
||||
userRejectedError,
|
||||
} from '../errors';
|
||||
import { type Transaction } from '../transaction-types';
|
||||
import {
|
||||
JsonRpcMethod,
|
||||
type Connector,
|
||||
type TransactionParams,
|
||||
type VegaWalletEvent,
|
||||
} from '../types';
|
||||
|
||||
enum EthereumMethod {
|
||||
@@ -43,7 +42,6 @@ declare global {
|
||||
type WindowEthereumProvider = {
|
||||
isMetaMask: boolean;
|
||||
request<T = unknown>(args: RequestArguments): Promise<T>;
|
||||
selectedAddress: string | null;
|
||||
};
|
||||
|
||||
interface Window {
|
||||
@@ -52,6 +50,16 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
interface SnapRPCError {
|
||||
code: number;
|
||||
message: string;
|
||||
data?: {
|
||||
originalError: { code: number };
|
||||
};
|
||||
}
|
||||
|
||||
const USER_REJECTED_CODE = -4;
|
||||
|
||||
export class SnapConnector implements Connector {
|
||||
readonly id = 'snap';
|
||||
readonly name = 'MetaMask Snap';
|
||||
@@ -61,8 +69,6 @@ export class SnapConnector implements Connector {
|
||||
node: string;
|
||||
version: string;
|
||||
snapId: string;
|
||||
pollRef: NodeJS.Timer | undefined;
|
||||
ee: EventEmitter;
|
||||
|
||||
// Note: apps may not know which node is selected on start up so its up
|
||||
// to the app to make sure class intances are renewed if the node changes
|
||||
@@ -70,14 +76,21 @@ export class SnapConnector implements Connector {
|
||||
this.node = config.node;
|
||||
this.version = config.version;
|
||||
this.snapId = config.snapId;
|
||||
this.ee = new EventEmitter();
|
||||
}
|
||||
|
||||
bindStore() {}
|
||||
|
||||
async connectWallet(desiredChainId: string) {
|
||||
try {
|
||||
await this.requestSnap();
|
||||
const res = await this.requestSnap();
|
||||
|
||||
if (res[this.snapId].blocked) {
|
||||
throw connectError('snap is blocked');
|
||||
}
|
||||
|
||||
if (!res[this.snapId].enabled) {
|
||||
throw connectError('snap is not enabled');
|
||||
}
|
||||
|
||||
const { chainId } = await this.getChainId();
|
||||
|
||||
@@ -87,7 +100,6 @@ export class SnapConnector implements Connector {
|
||||
);
|
||||
}
|
||||
|
||||
this.startPoll();
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
if (err instanceof ConnectorError) {
|
||||
@@ -98,57 +110,66 @@ export class SnapConnector implements Connector {
|
||||
}
|
||||
}
|
||||
|
||||
async disconnectWallet() {
|
||||
this.stopPoll();
|
||||
}
|
||||
async disconnectWallet() {}
|
||||
|
||||
// deprecated, pass chain on connect
|
||||
async getChainId() {
|
||||
try {
|
||||
const res = await this.invokeSnap<{ chainID: string }>(
|
||||
const data = await this.invokeSnap<{ chainID: string }>(
|
||||
JsonRpcMethod.GetChainId,
|
||||
{
|
||||
networkEndpoints: [this.node],
|
||||
}
|
||||
);
|
||||
return { chainId: res.chainID };
|
||||
|
||||
if ('error' in data) {
|
||||
throw chainIdError(data.error.message);
|
||||
}
|
||||
|
||||
return { chainId: data.chainID };
|
||||
} catch (err) {
|
||||
this.stopPoll();
|
||||
if (err instanceof ConnectorError) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw chainIdError();
|
||||
}
|
||||
}
|
||||
|
||||
async listKeys() {
|
||||
try {
|
||||
const res = await this.invokeSnap<{
|
||||
const data = await this.invokeSnap<{
|
||||
keys: Array<{ publicKey: string; name: string }>;
|
||||
}>(JsonRpcMethod.ListKeys);
|
||||
return res.keys;
|
||||
|
||||
if ('error' in data) {
|
||||
throw listKeysError(data.error.message);
|
||||
}
|
||||
|
||||
return data.keys;
|
||||
} catch (err) {
|
||||
this.stopPoll();
|
||||
if (err instanceof ConnectorError) {
|
||||
throw err;
|
||||
}
|
||||
throw listKeysError();
|
||||
}
|
||||
}
|
||||
|
||||
async isConnected() {
|
||||
try {
|
||||
// Check if metamask is unlocked
|
||||
if (!window.ethereum.selectedAddress) {
|
||||
throw noWalletError();
|
||||
}
|
||||
|
||||
// If this throws its likely the snap is disabled or has been uninstalled
|
||||
await this.listKeys();
|
||||
return { connected: true };
|
||||
} catch (err) {
|
||||
this.stopPoll();
|
||||
return { connected: false };
|
||||
}
|
||||
}
|
||||
|
||||
async sendTransaction(params: TransactionParams) {
|
||||
try {
|
||||
const res = await this.invokeSnap<{
|
||||
// If the transaction is invalid this will throw with SnapRPCError
|
||||
// but if its rejected it will resolve with 'error' in data
|
||||
const data = await this.invokeSnap<{
|
||||
transactionHash: string;
|
||||
transaction: { signature: { value: string } };
|
||||
receivedAt: string;
|
||||
@@ -160,115 +181,99 @@ export class SnapConnector implements Connector {
|
||||
networkEndpoints: [this.node],
|
||||
});
|
||||
|
||||
if ('error' in data) {
|
||||
if (data.error.code === USER_REJECTED_CODE) {
|
||||
throw userRejectedError();
|
||||
}
|
||||
|
||||
throw sendTransactionError(`${data.error.message}: ${data.error.data}`);
|
||||
}
|
||||
|
||||
return {
|
||||
transactionHash: res.transactionHash,
|
||||
signature: res.transaction.signature.value,
|
||||
receivedAt: res.receivedAt,
|
||||
sentAt: res.sentAt,
|
||||
transactionHash: data.transactionHash,
|
||||
signature: data.transaction.signature.value,
|
||||
receivedAt: data.receivedAt,
|
||||
sentAt: data.sentAt,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof ConnectorError) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (this.isSnapRPCError(err)) {
|
||||
throw sendTransactionError(err.message);
|
||||
}
|
||||
|
||||
throw sendTransactionError();
|
||||
}
|
||||
}
|
||||
|
||||
on(event: VegaWalletEvent, callback: () => void) {
|
||||
this.ee.on(event, callback);
|
||||
}
|
||||
on() {}
|
||||
|
||||
off() {}
|
||||
|
||||
off(event: VegaWalletEvent, callback?: () => void) {
|
||||
this.ee.off(event, callback);
|
||||
}
|
||||
////////////////////////////////////
|
||||
// Snap methods
|
||||
////////////////////////////////////
|
||||
|
||||
private startPoll() {
|
||||
// This only event we need to poll for right now is client.disconnect,
|
||||
// if more events get added we will need more logic here
|
||||
this.pollRef = setInterval(async () => {
|
||||
const result = await this.isConnected();
|
||||
if (result.connected) return;
|
||||
this.ee.emit('client.disconnected');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
private stopPoll() {
|
||||
if (this.pollRef) {
|
||||
clearInterval(this.pollRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests permission for a website to communicate with the specified snaps
|
||||
* and attempts to install them if they're not already installed.
|
||||
* If the installation of any snap fails, returns the error that caused the failure.
|
||||
* More informations here: https://docs.metamask.io/snaps/reference/rpc-api/#wallet_requestsnaps
|
||||
*/
|
||||
private async requestSnap() {
|
||||
await this.request(EthereumMethod.RequestSnaps, {
|
||||
[this.snapId]: {
|
||||
version: this.version,
|
||||
private async requestSnap(): Promise<{
|
||||
[snapId: string]: {
|
||||
blocked: boolean;
|
||||
enabled: boolean;
|
||||
id: string;
|
||||
version: string;
|
||||
};
|
||||
}> {
|
||||
return window.ethereum.request({
|
||||
method: EthereumMethod.RequestSnaps,
|
||||
params: {
|
||||
[this.snapId]: {
|
||||
version: this.version,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: check if this is needed, its used in use-snap-status
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Gets the list of all installed snaps.
|
||||
// * More information here: https://docs.metamask.io/snaps/reference/rpc-api/#wallet_getsnaps
|
||||
// */
|
||||
// async getSnap() {
|
||||
// const snaps = await this.request(EthereumMethod.GetSnaps);
|
||||
// return Object.values(snaps).find(
|
||||
// (s) => s.id === this.snapId && s.version === this.version
|
||||
// );
|
||||
// }
|
||||
|
||||
/**
|
||||
* Calls a method on the specified snap, always vega in this case
|
||||
* should always be npm:@vegaprotocol/snap
|
||||
*/
|
||||
private async invokeSnap<TResult>(
|
||||
method: JsonRpcMethod,
|
||||
params?: SnapInvocationParams
|
||||
): Promise<TResult> {
|
||||
return await this.request(EthereumMethod.InvokeSnap, {
|
||||
snapId: this.snapId,
|
||||
request: {
|
||||
method,
|
||||
params,
|
||||
params: SnapInvocationParams = {}
|
||||
): Promise<TResult | { error: SnapRPCError }> {
|
||||
// MetaMask in Firefox doesn't like undefined properties or some properties
|
||||
// on __proto__ so we need to strip them out with JSON.strinfify
|
||||
params = JSON.parse(JSON.stringify(params));
|
||||
|
||||
return window.ethereum.request({
|
||||
method: EthereumMethod.InvokeSnap,
|
||||
params: {
|
||||
snapId: this.snapId,
|
||||
request: {
|
||||
method,
|
||||
params,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls window.ethereum.request with method and params
|
||||
*/
|
||||
private async request<TResult>(
|
||||
method: EthereumMethod,
|
||||
params?: object
|
||||
): Promise<TResult> {
|
||||
if (window.ethereum?.request && window.ethereum?.isMetaMask) {
|
||||
// MetaMask in Firefox doesn't like undefined properties or some properties
|
||||
// on __proto__ so we need to strip them out with JSON.strinfify
|
||||
try {
|
||||
params = JSON.parse(JSON.stringify(params));
|
||||
} catch (err) {
|
||||
throw sendTransactionError();
|
||||
}
|
||||
|
||||
return window.ethereum.request({
|
||||
method,
|
||||
params,
|
||||
});
|
||||
private isSnapRPCError(obj: unknown): obj is SnapRPCError {
|
||||
if (
|
||||
obj !== undefined &&
|
||||
obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
'code' in obj &&
|
||||
'message' in obj
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
throw noWalletError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export class ConnectorError extends Error {
|
||||
|
||||
export const ConnectorErrors = {
|
||||
userRejected: { message: 'user rejected', code: 0 },
|
||||
noConnector: { message: 'no connector', code: 1 },
|
||||
noConnector: { message: 'not connected', code: 1 },
|
||||
connect: { message: 'failed to connect', code: 2 },
|
||||
disconnect: { message: 'failed to disconnect', code: 3 },
|
||||
chainId: { message: 'incorrect chain id', code: 4 },
|
||||
|
||||
@@ -492,6 +492,14 @@ export interface UpdateMarginMode {
|
||||
export interface UpdateMarginModeBody {
|
||||
updateMarginMode: UpdateMarginMode;
|
||||
}
|
||||
|
||||
export interface UpdatePartyProfile {
|
||||
updatePartyProfile: {
|
||||
alias: string;
|
||||
metadata: Array<{ key: string; value: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
export type Transaction =
|
||||
| UpdateMarginModeBody
|
||||
| StopOrdersSubmissionBody
|
||||
@@ -510,7 +518,8 @@ export type Transaction =
|
||||
| ApplyReferralCode
|
||||
| JoinTeam
|
||||
| CreateReferralSet
|
||||
| UpdateReferralSet;
|
||||
| UpdateReferralSet
|
||||
| UpdatePartyProfile;
|
||||
|
||||
export interface TransactionResponse {
|
||||
transactionHash: string;
|
||||
|
||||
@@ -93,7 +93,6 @@ describe('disconnect', () => {
|
||||
expect(result).toEqual({ status: 'disconnected' });
|
||||
expect(config.store.getState()).toMatchObject({
|
||||
status: 'disconnected',
|
||||
error: noConnectorError(),
|
||||
current: undefined,
|
||||
keys: [],
|
||||
pubKey: undefined,
|
||||
@@ -130,7 +129,7 @@ describe('refresh keys', () => {
|
||||
it('handles invalid connector', async () => {
|
||||
await config.refreshKeys();
|
||||
expect(config.store.getState()).toMatchObject({
|
||||
error: noConnectorError(),
|
||||
error: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -132,18 +132,20 @@ export function createConfig(cfg: Config): Wallet {
|
||||
store.setState(getInitialState(), true);
|
||||
return { status: 'disconnected' as const };
|
||||
} catch (err) {
|
||||
store.setState({
|
||||
...getInitialState(),
|
||||
error: err instanceof ConnectorError ? err : unknownError(),
|
||||
});
|
||||
store.setState(getInitialState(), true);
|
||||
return { status: 'disconnected' as const };
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshKeys() {
|
||||
const connector = connectors
|
||||
.getState()
|
||||
.find((x) => x.id === store.getState().current);
|
||||
const state = store.getState();
|
||||
const connector = connectors.getState().find((x) => x.id === state.current);
|
||||
|
||||
// Only refresh keys if connnected. If you aren't connect when you connect
|
||||
// you will get the latest keys
|
||||
if (state.status !== 'connected') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!connector) {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"build:all": "nx run-many --all --target=build",
|
||||
"build-spec:all": "nx run-many --all --target=build-spec",
|
||||
"lint:all": "nx run-many --all --target=lint",
|
||||
"e2e:all": "nx run-many --all --target=e2e",
|
||||
"vegacapsule": "vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl",
|
||||
"release": "git checkout develop ; git pull ; node scripts/make-release.js",
|
||||
"trading:test": "cd apps/trading/e2e && poetry run pytest -k",
|
||||
|
||||
+44
-35
@@ -7,18 +7,18 @@ projects = []
|
||||
projects_e2e = []
|
||||
|
||||
previews = {
|
||||
'governance': 'not deployed',
|
||||
'explorer': 'not deployed',
|
||||
'trading': 'not deployed',
|
||||
'tools': 'not deployed',
|
||||
'governance': 'not deployed',
|
||||
'explorer': 'not deployed',
|
||||
'trading': 'not deployed',
|
||||
'tools': 'not deployed',
|
||||
}
|
||||
|
||||
main_apps = ['governance', 'explorer', 'trading']
|
||||
|
||||
preview_governance="not deployed"
|
||||
preview_trading="not deployed"
|
||||
preview_explorer="not deployed"
|
||||
preview_tools="not deployed"
|
||||
preview_governance = "not deployed"
|
||||
preview_trading = "not deployed"
|
||||
preview_explorer = "not deployed"
|
||||
preview_tools = "not deployed"
|
||||
|
||||
# take input from the pipeline
|
||||
parser = ArgumentParser()
|
||||
@@ -30,7 +30,8 @@ parser.add_argument('--event-name', help='name of event in CI')
|
||||
args = parser.parse_args()
|
||||
|
||||
# run yarn affected command
|
||||
affected=check_output(f'yarn nx print-affected --base={environ["NX_BASE"]} --head={environ["NX_HEAD"]} --select=projects'.split()).decode('utf-8')
|
||||
affected = check_output(
|
||||
f'yarn nx print-affected --base={environ["NX_BASE"]} --head={environ["NX_HEAD"]} --select=projects'.split()).decode('utf-8')
|
||||
|
||||
|
||||
# print useful information
|
||||
@@ -44,46 +45,54 @@ print(affected)
|
||||
print(">>>> eof debug")
|
||||
|
||||
# define affection actions -> add to projects arrays and generate preview link
|
||||
|
||||
|
||||
def affect_app(app, preview_name=None):
|
||||
print(f"{app} is affected")
|
||||
projects.append(app)
|
||||
if not preview_name:
|
||||
preview_name=app
|
||||
previews[app] = f'https://{preview_name}.{args.branch_slug}.vega.rocks'
|
||||
print(f"{app} is affected")
|
||||
projects.append(app)
|
||||
if not preview_name:
|
||||
preview_name = app
|
||||
previews[app] = f'https://{preview_name}.{args.branch_slug}.vega.rocks'
|
||||
|
||||
|
||||
# check appearance in the affected string for main apps
|
||||
for app in main_apps:
|
||||
if app in affected:
|
||||
affect_app(app)
|
||||
if app in affected:
|
||||
affect_app(app)
|
||||
|
||||
# if non of main apps is affected - test all of them
|
||||
if not projects:
|
||||
for app in main_apps:
|
||||
affect_app(app)
|
||||
for app in main_apps:
|
||||
affect_app(app)
|
||||
|
||||
# generate e2e targets
|
||||
projects_e2e = [f'{app}-e2e' for app in projects]
|
||||
|
||||
# remove trading-e2e because it doesn't exists any more (new target is: console-e2e)
|
||||
if "trading-e2e" in projects_e2e:
|
||||
projects_e2e.remove("trading-e2e")
|
||||
|
||||
# check affection for multisig-signer which is deployed only from develop and pull requests
|
||||
if args.event_name == 'pull_request' or 'develop' in args.github_ref:
|
||||
if 'multisig-signer' in affected:
|
||||
affect_app('multisig-signer', 'tools')
|
||||
if 'multisig-signer' in affected:
|
||||
affect_app('multisig-signer', 'tools')
|
||||
|
||||
# now parse apps that are deployed from develop but don't have previews
|
||||
if 'develop' in args.github_ref:
|
||||
for app in ['static', 'ui-toolkit']:
|
||||
if app in affected:
|
||||
projects.append(app)
|
||||
for app in ['static', 'ui-toolkit']:
|
||||
if app in affected:
|
||||
projects.append(app)
|
||||
|
||||
# if ref is in format release/{env}-{app} then only {app} is deployed
|
||||
if 'release' in args.github_ref:
|
||||
for app in main_apps:
|
||||
if f'{args.github_ref}'.endswith(app):
|
||||
projects = [app]
|
||||
projects_e2e = [f'{app}-e2e']
|
||||
for app in main_apps:
|
||||
if f'{args.github_ref}'.endswith(app):
|
||||
projects = [app]
|
||||
projects_e2e = [f'{app}-e2e']
|
||||
|
||||
|
||||
projects = json.dumps(projects)
|
||||
|
||||
projects_e2e = json.dumps(projects_e2e)
|
||||
|
||||
print(f'Projects: {projects}')
|
||||
@@ -91,20 +100,20 @@ print(f'Projects E2E: {projects_e2e}')
|
||||
|
||||
print('>> Previews')
|
||||
for preview, preview_value in previews.items():
|
||||
print(f'{preview}: {preview_value}')
|
||||
print(f'{preview}: {preview_value}')
|
||||
print('>> EOF Previews')
|
||||
|
||||
|
||||
lines_to_write = [
|
||||
f'PREVIEW_GOVERNANCE={previews["governance"]}',
|
||||
f'PREVIEW_EXPLORER={previews["explorer"]}',
|
||||
f'PREVIEW_TRADING={previews["trading"]}',
|
||||
f'PREVIEW_TOOLS={previews["tools"]}',
|
||||
f'PROJECTS={projects}',
|
||||
f'PROJECTS_E2E={projects_e2e}',
|
||||
f'PREVIEW_GOVERNANCE={previews["governance"]}',
|
||||
f'PREVIEW_EXPLORER={previews["explorer"]}',
|
||||
f'PREVIEW_TRADING={previews["trading"]}',
|
||||
f'PREVIEW_TOOLS={previews["tools"]}',
|
||||
f'PROJECTS={projects}',
|
||||
f'PROJECTS_E2E={projects_e2e}',
|
||||
]
|
||||
env_file = environ['GITHUB_ENV']
|
||||
print(f'Line to add to GITHUB_ENV file: {env_file}')
|
||||
print(lines_to_write)
|
||||
with open(env_file, 'a') as _f:
|
||||
_f.write('\n'.join(lines_to_write))
|
||||
_f.write('\n'.join(lines_to_write))
|
||||
|
||||
Reference in New Issue
Block a user