Compare commits

...
Author SHA1 Message Date
Madalina Raicu 0d8176e9b4 fix: update gov and explor. metadata & remove second side 2023-08-11 11:14:11 +03:00
Madalina Raicu d515186bd0 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/metadata-console 2023-08-11 11:12:32 +03:00
m.ray 1b311a8b36 fix(trading): order single view side (#4526) 2023-08-11 09:12:13 +01:00
Madalina Raicu f04bfec1a9 fix: revert env 2023-08-11 11:04:51 +03:00
Madalina Raicu 2acabb3043 fix: add assets for metadata 2023-08-11 11:04:06 +03:00
Madalina Raicu 1296faab8d fix: add assets for metadata 2023-08-11 11:03:45 +03:00
Madalina Raicu 1e39248d63 fix: index page tsx grid 2023-08-11 10:59:42 +03:00
Madalina Raicu 22c893dc73 chore: update metadata 2023-08-10 23:45:37 +03:00
Madalina Raicu a1a70887ec Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/metadata-console 2023-08-10 23:37:39 +03:00
Matthew Russell 70c3bc8128 chore(trading): remove floating buttons (#4512) 2023-08-10 16:02:46 +01:00
Joe Tsang 7e49836875 chore(governance): add e2e for successor market diff (#4519) 2023-08-10 15:03:23 +01:00
Maciek d624957e6d chore(trading): 4384 all markets in switcher (#4516) 2023-08-10 14:25:13 +01:00
Maciekandasiaznik 069b57d4ae feat(wallet): 4370 new connect wallet modal (#4492)
Co-authored-by: asiaznik <artur@vegaprotocol.io>
2023-08-10 13:03:53 +02:00
Madalina Raicu d537b2a510 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/metadata-console 2023-08-09 17:46:20 +03:00
Madalina Raicu 9ff43f9630 feat(trading): metadata explorer & governance 2023-08-09 17:13:16 +03:00
Madalina Raicu b5996eae39 feat(trading): metadata console 2023-08-09 17:12:44 +03:00
Madalina Raicu 8b33e9a099 feat(trading): metadata console 2023-08-09 16:36:38 +03:00
Madalina Raicu ab1a2cb222 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into develop 2023-08-07 16:22:11 +01:00
Madalina Raicu e1a1ae0fa1 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into develop 2023-08-07 10:16:03 +01:00
98 changed files with 1654 additions and 1307 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+20
View File
@@ -0,0 +1,20 @@
{
"short_name": "Mainnet Stats",
"name": "Vega Mainnet statistics",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+27 -11
View File
@@ -1,23 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Explorer</title>
<base href="/" />
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Vega Protocol - VEGA Explorer" />
<meta name="og:type" content="website" />
<meta name="og:url" content="https://explorer.vega.xyz/" />
<meta name="og:title" content="Vega Protocol - Explorer" />
<meta name="og:site_name" content="Vega Protocol - Explorer" />
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Vega Protocol - Explorer" />
<meta name="twitter:description" content="Vega Protocol - Explorer" />
<meta name="twitter:image" content="https://static.vega.xyz/favicon.ico" />
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
<meta name="description" content="Vega Protocol - Explorer" />
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
<link rel="manifest" href="assets/manifest.json" />
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
/>
<title>VEGA Explorer</title>
<script src="./assets/env-config.js"></script>
</head>
<body class="dark:bg-black h-full w-full">
+1
View File
@@ -18,6 +18,7 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
NX_SUCCESSOR_MARKETS=true
#Test configuration variables
CYPRESS_FAIRGROUND=false
@@ -11,7 +11,9 @@ import {
getDateFormatForSpecifiedDays,
getProposalFromTitle,
getProposalInformationFromTable,
proposalChangeType,
submitUniqueRawProposal,
validateProposalDetailsDiff,
voteForProposal,
} from '../../../../governance-e2e/src/support/governance.functions';
import {
@@ -26,7 +28,9 @@ import {
} from '../../support/wallet-functions';
import type { testFreeformProposal } from '../../support/common-interfaces';
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
const proposalListItem = '[data-testid="proposals-list-item"]';
const proposalVoteProgressForPercentage =
'vote-progress-indicator-percentage-for';
const proposalVoteProgressAgainstPercentage =
@@ -42,6 +46,7 @@ const viewProposalButton = 'view-proposal-btn';
const proposalDescriptionToggle = 'proposal-description-toggle';
const voteBreakdownToggle = 'vote-breakdown-toggle';
const proposalTermsToggle = 'proposal-json-toggle';
const marketDataToggle = 'proposal-market-data-toggle';
describe(
'Governance flow for proposal details',
@@ -354,5 +359,98 @@ describe(
switchVegaWalletPubKey();
stakingPageDisassociateAllTokens();
});
it('Able to see successor market details with new and updated values', function () {
cy.createMarket();
cy.reload();
waitForSpinner();
cy.getByTestId('closed-proposals').within(() => {
cy.contains('Add Lorem Ipsum market')
.parentsUntil(proposalListItem)
.last()
.within(() => {
cy.getByTestId(viewProposalButton).click();
});
});
getProposalInformationFromTable('ID')
.invoke('text')
.as('parentMarketId')
.then(() => {
cy.VegaWalletSubmitProposal(
createSuccessorMarketProposalTxBody(this.parentMarketId)
);
});
navigateTo(navigation.proposals);
cy.reload();
getProposalFromTitle('Test successor market proposal details').within(
() => cy.getByTestId(viewProposalButton).click()
);
// #3003-PMAN-010
cy.getByTestId(proposalTermsToggle).click();
cy.get('.language-json').within(() => {
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
cy.get('.hljs-string').should('contain.text', '0.75');
});
// 3003-PMAN-011 3003-PMAN-012
cy.getByTestId(marketDataToggle).click();
cy.getByTestId('proposal-market-data').within(() => {
cy.contains('Key details').click();
validateProposalDetailsDiff(
'Name',
proposalChangeType.UPDATED,
'Token test market',
'Test market 1'
);
validateProposalDetailsDiff(
'Parent Market ID',
proposalChangeType.ADDED,
this.parentMarketId
);
validateProposalDetailsDiff(
'Insurance Pool Fraction',
proposalChangeType.ADDED,
'0.75'
);
validateProposalDetailsDiff(
'Trading Mode',
proposalChangeType.UPDATED,
'No trading',
'Opening auction'
);
cy.contains('Instrument').click();
validateProposalDetailsDiff(
'Market Name',
proposalChangeType.UPDATED,
'Token test market',
'Test market 1'
);
cy.contains('Metadata').click();
validateProposalDetailsDiff(
'Sector',
proposalChangeType.UPDATED,
'materials',
'tech'
);
});
// 3003-PMAN-011
cy.get('.underline').contains('Parent Market ID').realHover();
cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
'contain.text',
'The ID of the market this market succeeds.'
);
cy.get('.underline')
.contains('Insurance Pool Fraction')
.realMouseUp()
.realHover();
cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
'contain.text',
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
);
});
}
);
@@ -654,44 +654,7 @@ context(
.find('a')
.should('have.attr', 'href')
.and('contain', this.parentMarketId);
cy.getByTestId('view-proposal-btn').click();
});
// #3003-PMAN-010
cy.getByTestId(proposalJsonToggle).click();
cy.get('.language-json').within(() => {
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
cy.get('.hljs-string').should('contain.text', '0.75');
});
cy.getByTestId('proposal-market-data').within(() => {
cy.getByTestId('proposal-market-data-toggle').click();
cy.contains('Key details').click();
// 3003-PMAN-009
getMarketProposalDetailsFromTable('Parent Market ID').should(
'have.text',
this.parentMarketId
);
getMarketProposalDetailsFromTable('Insurance Pool Fraction').should(
'have.text',
'0.75'
);
getMarketProposalDetailsFromTable('Trading Mode').should(
'have.text',
'No trading'
);
});
// 3003-PMAN-011
cy.contains('Parent Market ID').realHover();
cy.getByTestId('tooltip-content').should(
'contain.text',
'The ID of the market this market succeeds.'
);
cy.contains('Insurance Pool Fraction').realHover();
cy.getByTestId('tooltip-content').should(
'contain.text',
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
);
});
after('Disassociate from second wallet key if present', function () {
@@ -25,6 +25,9 @@ import {
vegaWalletSetSpecifiedApprovalAmount,
vegaWalletTeardown,
} from '../../support/wallet-functions';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const stakeValidatorListTotalStake = 'total-stake';
const stakeValidatorListTotalShare = 'total-stake-share';
const stakeValidatorListStakePercentage = 'stake-percentage';
@@ -55,6 +58,10 @@ context(
function () {
// 1002-STKE-002, 1002-STKE-032
before('visit staking tab and connect vega wallet', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/');
ethereumWalletConnect();
cy.connectVegaWallet();
@@ -65,6 +72,10 @@ context(
beforeEach(
'teardown wallet & drill into a specific validator',
function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.clearLocalStorage();
turnTelemetryOff();
// Go to homepage to allow wallet teardown without epoch timer refreshing page
@@ -6,6 +6,8 @@ import {
} from '../../support/common.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { depositAsset } from '../../support/wallet-functions';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const withdraw = 'withdraw';
const withdrawalForm = 'withdraw-form';
@@ -42,12 +44,20 @@ context(
{ tags: '@slow' },
function () {
before('visit withdrawals and connect vega wallet', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/');
ethereumWalletConnect();
depositAsset(usdcEthAddress, '1000', 5);
});
beforeEach('Navigate to withdrawal page', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.clearLocalStorage();
turnTelemetryOff();
cy.reload();
@@ -11,6 +11,7 @@ import {
waitForBeginningOfEpoch,
} from '../../support/staking.functions';
import { previousEpochData } from '../../fixtures/mocks/previous-epoch';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const guideLink = 'staking-guide-link';
const validatorTitle = 'validator-node-title';
@@ -33,11 +34,22 @@ const overstakedPenaltyToolTip = 'overstaked-penalty-tooltip';
const multisigPenaltyToolTip = 'multisig-error-tooltip';
const epochCountDown = 'epoch-countdown';
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/;
const txTimeout = Cypress.env('txTimeout');
context('Validators Page - verify elements on page', function () {
before('navigate to validators page', function () {
before('navigate to validators page', () => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/validators');
});
beforeEach(() => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
});
describe('with wallets disconnected', { tags: '@smoke' }, function () {
it('Should have validators tab highlighted', function () {
@@ -177,6 +189,11 @@ context('Validators Page - verify elements on page', function () {
{ tags: '@smoke' },
function () {
before('connect wallets and click on validator', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/validators');
cy.connectVegaWallet();
clickOnValidatorFromList(0);
});
@@ -263,7 +280,7 @@ context('Validators Page - verify elements on page', function () {
cy.getByTestId(epochCountDown).within(() => {
cy.get(epochTitle).should('not.be.empty');
cy.get(nextEpochInfo).should('contain.text', 'Next epoch');
cy.get(nextEpochInfo, txTimeout).should('contain.text', 'Next epoch');
});
});
}
@@ -4,6 +4,8 @@ import {
vegaWalletFaucetAssetsWithoutCheck,
vegaWalletTeardown,
} from '../../support/wallet-functions';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const walletContainer = 'aside [data-testid="vega-wallet"]';
const walletHeader = '[data-testid="wallet-header"] h1';
@@ -14,10 +16,6 @@ const dialogHeader = 'dialog-title';
const walletDialogHeader = 'wallet-dialog-title';
const connectorsList = 'connectors-list';
const dialogCloseBtn = 'dialog-close';
const restConnectorForm = 'rest-connector-form';
const restWallet = '#wallet';
const restPassphrase = '#passphrase';
const restConnectBtn = '[type="submit"]';
const accountNo = 'vega-account-truncated';
const currencyTitle = 'currency-title';
const currencyValue = 'currency-value';
@@ -69,7 +67,7 @@ context(
cy.get(dialog).within(() => {
cy.getByTestId(walletDialogHeader)
.should('be.visible')
.and('have.text', 'Connect');
.and('have.text', 'Get a Vega wallet');
});
});
@@ -77,10 +75,7 @@ context(
cy.getByTestId(connectorsList).within(() => {
cy.getByTestId('connector-jsonRpc')
.should('be.visible')
.and('have.text', 'Connect Vega wallet');
cy.getByTestId('connector-rest')
.should('be.visible')
.and('have.text', 'Hosted Fairground wallet');
.and('have.text', 'Use the Desktop App/CLI');
});
});
@@ -91,48 +86,14 @@ context(
});
});
describe('when rest connector form opened', function () {
before('click hosted wallet app button', function () {
cy.getByTestId(connectorsList).within(() => {
cy.getByTestId('connector-rest').click();
});
});
// 0002-WCON-002
it('should have wallet field visible', function () {
cy.getByTestId(restConnectorForm).within(() => {
cy.get(restWallet).should('be.visible');
});
});
it('should have password field visible', function () {
cy.getByTestId(restConnectorForm).within(() => {
cy.get(restPassphrase).should('be.visible');
});
});
it('should have connect button visible', function () {
cy.getByTestId(restConnectorForm).within(() => {
cy.get(restConnectBtn)
.should('be.visible')
.and('have.text', 'Connect');
});
});
it('should have close button visible', function () {
cy.get(dialog).within(() => {
cy.getByTestId(dialogCloseBtn).should('be.visible');
});
});
after('close dialog', function () {
cy.getByTestId(dialogCloseBtn).click().should('not.exist');
});
});
describe('when vega wallet connected', function () {
before('connect vega wallet', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/');
cy.wait('@ChainId');
cy.connectVegaWallet();
vegaWalletTeardown();
});
@@ -315,6 +276,10 @@ context(
];
before('faucet assets to connected vega wallet', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
for (const { id, amount } of assets) {
vegaWalletFaucetAssetsWithoutCheck(id, amount, vegaWalletPublicKey);
}
@@ -232,6 +232,23 @@ export function getDownloadedProposalJsonPath(proposalType: string) {
return filepath;
}
export function validateProposalDetailsDiff(
RowName: string,
changeType: proposalChangeType,
newValue: string,
oldValue?: string
) {
cy.contains(RowName)
.parentsUntil(proposalInformationTableRows)
.parent()
.first()
.within(() => {
cy.contains(changeType).should('be.visible');
cy.contains(newValue).should('be.visible');
if (oldValue) cy.contains(oldValue).should('have.class', 'line-through');
});
}
function getFormattedTime() {
const now = new Date();
const day = now.getDate().toString().padStart(2, '0');
@@ -252,3 +269,8 @@ export enum governanceProposalType {
FREEFORM = 'Freeform',
RAW = 'raw proposal',
}
export enum proposalChangeType {
UPDATED = 'Updated',
ADDED = 'Added',
}
@@ -211,6 +211,143 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
};
}
// Requires cy.createMarket() to be run to set up parent market
export function createSuccessorMarketProposalTxBody(
parentMarketId: string
): ProposalSubmissionBody {
const MIN_CLOSE_SEC = 10000;
const MIN_ENACT_SEC = 10000;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
return {
proposalSubmission: {
rationale: {
title: 'Test successor market proposal details',
description: 'E2E test for successor market',
},
terms: {
newMarket: {
changes: {
decimalPlaces: '5',
positionDecimalPlaces: '5',
linearSlippageFactor: '0.001',
quadraticSlippageFactor: '0',
lpPriceRange: '10',
instrument: {
name: 'Token test market',
code: 'TEST.24h',
future: {
settlementAsset:
'816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
quoteName: 'fUSDC',
dataSourceSpecForSettlementData: {
external: {
oracle: {
signers: [
{
pubKey: {
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
},
},
],
filters: [
{
key: {
name: 'prices.BTC.value',
type: 'TYPE_INTEGER' as const,
numberDecimalPlaces: '0',
},
conditions: [
{
operator: 'OPERATOR_GREATER_THAN' as const,
value: '0',
},
],
},
],
},
},
},
dataSourceSpecForTradingTermination: {
external: {
oracle: {
signers: [
{
pubKey: {
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
},
},
],
filters: [
{
key: {
name: 'trading.terminated.ETH5',
type: 'TYPE_BOOLEAN' as const,
},
conditions: [
{
operator: 'OPERATOR_EQUALS' as const,
value: 'true',
},
],
},
],
},
},
},
dataSourceSpecBinding: {
settlementDataProperty: 'prices.BTC.value',
tradingTerminationProperty: 'trading.terminated.ETH5',
},
},
},
metadata: [
'sector:food',
'sector:materials',
'source:docs.vega.xyz',
],
priceMonitoringParameters: {
triggers: [
{
horizon: '43200',
probability: '0.9999999',
auctionExtension: '600',
},
],
},
liquidityMonitoringParameters: {
targetStakeParameters: {
timeWindow: '3600',
scalingFactor: 10,
},
triggeringRatio: '0.7',
auctionExtension: '1',
},
logNormal: {
tau: 0.0001140771161,
riskAversionParameter: 0.01,
params: {
mu: 0,
r: 0.016,
sigma: 0.5,
},
},
successor: {
parentMarketId: parentMarketId,
insurancePoolFraction: '0.75',
},
},
},
closingTimestamp,
enactmentTimestamp,
},
},
};
}
export function mockNetworkUpgradeProposal() {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'Nodes', nodeData);
+4 -1
View File
@@ -21,9 +21,12 @@ NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
#Test configuration variables
CYPRESS_FAIRGROUND=false
LC_ALL="en_US.UTF-8"
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_SUCCESSOR_MARKETS=true
@@ -15,6 +15,7 @@ export const VegaWalletPrompt = () => {
</Link>
<ButtonLink
className="text-neutral-500"
data-testid="view-as-user"
onClick={() => setViewAsDialog(true)}
>
{t('viewAsParty')}
+20 -13
View File
@@ -1,8 +1,8 @@
<!DOCTYPE html>
<html lang="en" class="dark bg-black w-full h-full">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
<link rel="stylesheet" href="//static.vega.xyz/fonts.css" />
<link
rel="icon"
type="image/x-icon"
@@ -10,35 +10,42 @@
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Vega Protocol - VEGA Token Vesting" />
<meta name="description" content="Vega Protocol - VEGA Governance" />
<meta name="og:type" content="website" />
<meta name="og:url" content="https://governance.vega.xyz/" />
<meta name="og:title" content="Vega Protocol - Governance" />
<meta name="og:site_name" content="Vega Protocol - Governance" />
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Vega Protocol - Governance" />
<meta name="twitter:description" content="Vega Protocol - Governance" />
<meta name="twitter:image" content="https://static.vega.xyz/favicon.ico" />
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
<meta name="description" content="Vega Protocol - Governance" />
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="assets/manifest.json" />
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<title>Vega Governance dApp</title>
<title>VEGA Governance</title>
<script src="./assets/env-config.js"></script>
</head>
<body class="h-full">
<noscript>You need to enable JavaScript to run this app.</noscript>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root" class="h-full"></div>
<!--
This HTML file is a template.
<!-- This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
To create a production bundle, use `npm run build` or `yarn build`. -->
</body>
</html>
@@ -1,5 +1,4 @@
import {
RestConnector,
JsonRpcConnector,
ViewConnector,
InjectedConnector,
@@ -8,13 +7,11 @@ import {
const urlParams = new URLSearchParams(window.location.search);
export const injected = new InjectedConnector();
export const rest = new RestConnector();
export const jsonRpc = new JsonRpcConnector();
export const view = new ViewConnector(urlParams.get('address'));
export const Connectors = {
injected,
rest,
jsonRpc,
view,
};
+4 -5
View File
@@ -169,14 +169,13 @@ describe('home', { tags: '@regression' }, () => {
it('click get started button should open connect dialog', () => {
cy.getByTestId('welcome-dialog').should('be.visible');
cy.getByTestId('get-started-button').click();
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
cy.window().then((window) => {
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
'true'
);
// @ts-ignore stub it out just for test case
window.vega = {};
cy.getByTestId('get-started-button').click();
cy.getByTestId('wallet-dialog-title').should('contain.text', 'Connect');
});
cy.getByTestId('wallet-dialog-title').should('contain.text', 'Connect');
});
});
@@ -5,118 +5,8 @@ import {
const connectVegaBtn = 'connect-vega-wallet';
const manageVegaBtn = 'manage-vega-wallet';
const form = 'rest-connector-form';
const dialogContent = 'dialog-content';
describe(
'connect hosted wallet',
{ tags: '@smoke', testIsolation: true },
() => {
beforeEach(() => {
// Using portfolio page as it requires vega wallet connection
cy.visit('/#/portfolio');
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
});
it('can connect', () => {
// 0002-WCON-002
// 0002-WCON-003
// 0002-WCON-039
// 0002-WCON-017
// 0002-WCON-018
// 0002-WCON-019
// Mock authentication
cy.intercept(
'POST',
'https://wallet.testnet.vega.xyz/api/v1/auth/token',
{
body: {
token: 'test-token',
},
}
);
// Mock getting keys from wallet
cy.intercept('GET', 'https://wallet.testnet.vega.xyz/api/v1/keys', {
body: {
keys: [
{
algorithm: {
name: 'algo',
version: 1,
},
index: 0,
meta: [],
pub: 'HOSTED_PUBKEY',
tainted: false,
},
],
},
});
cy.getByTestId(connectVegaBtn).click();
cy.contains(
'Choose wallet app to connect, or to change port or server URL enter a custom wallet location first'
);
cy.contains('Connect Vega wallet');
cy.contains('Hosted Fairground wallet');
cy.getByTestId('connectors-list')
.find('[data-testid="connector-rest"]')
.click();
cy.getByTestId(form).find('#wallet').click().type('user');
cy.getByTestId(form).find('#passphrase').click().type('pass');
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId(manageVegaBtn).should('exist');
cy.getByTestId('manage-vega-wallet').click();
cy.getByTestId('keypair-list').should('exist');
});
it('doesnt connect with invalid credentials', () => {
// 0002-WCON-020
// Mock incorrect username/password
cy.intercept(
'POST',
'https://wallet.testnet.vega.xyz/api/v1/auth/token',
{
body: {
error: 'No wallet',
},
statusCode: 403, // 403 forbidden invalid crednetials
}
);
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-rest"]')
.click();
cy.getByTestId(form).find('#wallet').click().type('invalid name');
cy.getByTestId(form).find('#passphrase').click().type('invalid password');
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId('form-error').should('have.text', 'Invalid credentials');
});
it('doesnt connect with empty fields', () => {
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-rest"]')
.click();
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId(form)
.find('#wallet')
.next('[data-testid="input-error-text"]')
.should('have.text', 'Required');
cy.getByTestId(form)
.find('#passphrase')
.next('[data-testid="input-error-text"]')
.should('have.text', 'Required');
});
}
);
describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
beforeEach(() => {
// Using portfolio page as it requires vega wallet connection
@@ -196,8 +86,6 @@ describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
cy.getByTestId('connect-vega-wallet').should('exist');
cy.getByTestId('manage-vega-wallet').should('not.exist');
cy.getByTestId('connect-vega-wallet').click();
cy.contains(
'Choose wallet app to connect, or to change port or server URL enter a custom wallet location first'
);
cy.contains('Enter a custom wallet location');
});
});
+3 -2
View File
@@ -1,4 +1,3 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
@@ -13,9 +12,11 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
# NX_PRODUCT_PERPETUALS
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

+1
View File
@@ -0,0 +1 @@
window._env_ = {};
Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+20
View File
@@ -0,0 +1,20 @@
{
"short_name": "Mainnet Stats",
"name": "Vega Mainnet statistics",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+4 -7
View File
@@ -10,7 +10,6 @@ import { TradeGrid } from './trade-grid';
import { TradePanels } from './trade-panels';
import { useNavigate, useParams } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { ViewType, useSidebar } from '../../components/sidebar';
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
@@ -65,8 +64,6 @@ export const MarketPage = () => {
const update = useGlobalStore((store) => store.update);
const lastMarketId = useGlobalStore((store) => store.marketId);
const onSelect = useMarketClickHandler();
const { data, error, loading } = useMarket(marketId);
useEffect(() => {
@@ -87,7 +84,6 @@ export const MarketPage = () => {
return (
<TradeGrid
market={data}
onSelect={onSelect}
pinnedAsset={
data?.tradableInstrument.instrument.product.settlementAsset
}
@@ -97,11 +93,12 @@ export const MarketPage = () => {
return (
<TradePanels
market={data}
onSelect={onSelect}
onClickCollateral={() => navigate('/portfolio')}
pinnedAsset={
data?.tradableInstrument.instrument.product.settlementAsset
}
/>
);
}, [largeScreen, data, onSelect, navigate]);
}, [largeScreen, data]);
if (!data && marketId) {
return (
+22 -31
View File
@@ -9,7 +9,6 @@ import { OracleBanner } from '@vegaprotocol/markets';
import type { Market } from '@vegaprotocol/markets';
import { Filter } from '@vegaprotocol/orders';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import {
ResizableGrid,
ResizableGridPanel,
@@ -24,7 +23,6 @@ import { FLAGS } from '@vegaprotocol/environment';
interface TradeGridProps {
market: Market | null;
onSelect: (marketId: string, metaKey?: boolean) => void;
pinnedAsset?: PinnedAsset;
}
@@ -40,7 +38,6 @@ const MainGrid = memo(
const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({
id: 'middle-1',
});
const onMarketClick = useMarketClickHandler(true);
return (
<ResizableGrid vertical onChange={handleOnLayoutChange}>
@@ -98,30 +95,27 @@ const MainGrid = memo(
<TradeGridChild>
<Tabs storageKey="console-trade-grid-bottom">
<Tab id="positions" name={t('Positions')}>
<TradingViews.positions.component
onMarketClick={onMarketClick}
/>
<TradingViews.positions.component />
</Tab>
<Tab id="open-orders" name={t('Open')}>
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Open}
/>
<Tab
id="open-orders"
name={t('Open')}
menu={<TradingViews.activeOrders.menu marketId={marketId} />}
>
<TradingViews.orders.component filter={Filter.Open} />
</Tab>
<Tab id="closed-orders" name={t('Closed')}>
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Closed}
/>
<TradingViews.orders.component filter={Filter.Closed} />
</Tab>
<Tab id="rejected-orders" name={t('Rejected')}>
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Rejected}
/>
<TradingViews.orders.component filter={Filter.Rejected} />
</Tab>
<Tab id="orders" name={t('All')}>
<TradingViews.orders.component marketId={marketId} />
<Tab
id="orders"
name={t('All')}
menu={<TradingViews.orders.menu marketId={marketId} />}
>
<TradingViews.orders.component />
</Tab>
{FLAGS.STOP_ORDERS ? (
<Tab id="stop-orders" name={t('Stop orders')}>
@@ -129,17 +123,14 @@ const MainGrid = memo(
</Tab>
) : null}
<Tab id="fills" name={t('Fills')}>
<TradingViews.fills.component
marketId={marketId}
onMarketClick={onMarketClick}
/>
<TradingViews.fills.component marketId={marketId} />
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<TradingViews.collateral.component
pinnedAsset={pinnedAsset}
onMarketClick={onMarketClick}
hideButtons
/>
<Tab
id="accounts"
name={t('Collateral')}
menu={<TradingViews.collateral.menu />}
>
<TradingViews.collateral.component pinnedAsset={pinnedAsset} />
</Tab>
</Tabs>
</TradeGridChild>
@@ -1,13 +1,9 @@
import type { PinnedAsset } from '@vegaprotocol/accounts';
import type { Market } from '@vegaprotocol/markets';
import { OracleBanner } from '@vegaprotocol/markets';
import {
useMarketClickHandler,
useMarketLiquidityClickHandler,
} from '../../lib/hooks/use-market-click-handler';
import type { TradingView } from './trade-views';
import { TradingViews } from './trade-views';
import { memo, useState } from 'react';
import { useState } from 'react';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { NO_MARKET } from './constants';
import AutoSizer from 'react-virtualized-auto-sizer';
@@ -20,33 +16,14 @@ import { FLAGS } from '@vegaprotocol/environment';
interface TradePanelsProps {
market: Market | null;
onSelect: (marketId: string, metaKey?: boolean) => void;
onMarketClick?: (marketId: string) => void;
onOrderTypeClick?: (marketId: string) => void;
onClickCollateral: () => void;
pinnedAsset?: PinnedAsset;
}
export const TradePanels = ({
market,
onSelect,
onClickCollateral,
pinnedAsset,
}: TradePanelsProps) => {
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
const [view, setView] = useState<TradingView>('candles');
const renderView = () => {
const Component = memo<{
marketId: string;
onSelect: (marketId: string, metaKey?: boolean) => void;
onMarketClick?: (marketId: string) => void;
onOrderTypeClick?: (marketId: string) => void;
onClickCollateral: () => void;
pinnedAsset?: PinnedAsset;
}>(TradingViews[view].component);
const Component = TradingViews[view].component;
if (!Component) {
throw new Error(`No component for view: ${view}`);
@@ -54,16 +31,7 @@ export const TradePanels = ({
if (!market) return <Splash>{NO_MARKET}</Splash>;
return (
<Component
marketId={market?.id}
onSelect={onSelect}
onClickCollateral={onClickCollateral}
pinnedAsset={pinnedAsset}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
/>
);
return <Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
};
const renderMenu = () => {
@@ -71,9 +39,10 @@ export const TradePanels = ({
if ('menu' in viewCfg) {
const Menu = viewCfg.menu;
return (
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
<Menu />
<Menu marketId={market?.id || ''} />
</div>
);
}
@@ -6,7 +6,7 @@ import {
CandlesChartContainer,
CandlesMenu,
} from '@vegaprotocol/candles-chart';
import { Filter } from '@vegaprotocol/orders';
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
import { NO_MARKET } from './constants';
import { OrderbookContainer } from '../../components/orderbook-container';
import { FillsContainer } from '../../components/fills-container';
@@ -16,6 +16,7 @@ import { LiquidityContainer } from '../../components/liquidity-container';
import type { OrderContainerProps } from '../../components/orders-container';
import { OrdersContainer } from '../../components/orders-container';
import { StopOrdersContainer } from '../../components/stop-orders-container';
import { AccountsMenu } from '../../components/accounts-menu';
type MarketDependantView =
| typeof CandlesChartContainer
@@ -62,6 +63,7 @@ export const TradingViews = {
component: (props: OrderContainerProps) => (
<OrdersContainer {...props} filter={Filter.Open} />
),
menu: OpenOrdersMenu,
},
closedOrders: {
label: 'Closed',
@@ -78,11 +80,16 @@ export const TradingViews = {
orders: {
label: 'All',
component: OrdersContainer,
menu: OpenOrdersMenu,
},
stopOrders: {
label: 'Stop',
component: StopOrdersContainer,
},
collateral: { label: 'Collateral', component: AccountsContainer },
collateral: {
label: 'Collateral',
component: AccountsContainer,
menu: AccountsMenu,
},
fills: { label: 'Fills', component: FillsContainer },
};
@@ -411,24 +411,26 @@ describe('Closed', () => {
},
},
};
render(
<MemoryRouter>
<MockedProvider
mocks={[
mixedMarketsMock,
marketsDataMock,
oracleDataMock,
successorMarketsMock,
]}
>
<VegaWalletContext.Provider
value={{ pubKey } as VegaWalletContextShape}
await act(() => {
render(
<MemoryRouter>
<MockedProvider
mocks={[
mixedMarketsMock,
marketsDataMock,
oracleDataMock,
successorMarketsMock,
]}
>
<Closed />
</VegaWalletContext.Provider>
</MockedProvider>
</MemoryRouter>
);
<VegaWalletContext.Provider
value={{ pubKey } as VegaWalletContextShape}
>
<Closed />
</VegaWalletContext.Provider>
</MockedProvider>
</MemoryRouter>
);
});
await waitFor(() => {
expect(
@@ -1,40 +0,0 @@
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import { DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useSidebar, ViewType } from '../../components/sidebar';
export const DepositsContainer = () => {
const { pubKey, isReadOnly } = useVegaWallet();
const { data, error } = useDataProvider({
dataProvider: depositsProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const setView = useSidebar((store) => store.setView);
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<div className="h-full">
<DepositsTable
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
/>
{!isReadOnly && (
<div className="h-auto flex justify-end p-2 bottom-0 right-0 absolute dark:bg-black/75 bg-white/75 rounded">
<Button
variant="primary"
size="sm"
onClick={() => setView({ type: ViewType.Deposit })}
data-testid="deposit-button"
>
{t('Deposit')}
</Button>
</div>
)}
</div>
);
};
@@ -6,12 +6,11 @@ import { t } from '@vegaprotocol/i18n';
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { usePageTitleStore } from '../../stores';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { AccountsContainer } from '../../components/accounts-container';
import { DepositsContainer } from './deposits-container';
import { DepositsContainer } from '../../components/deposits-container';
import { FillsContainer } from '../../components/fills-container';
import { PositionsContainer } from '../../components/positions-container';
import { WithdrawalsContainer } from './withdrawals-container';
import { WithdrawalsContainer } from '../../components/withdrawals-container';
import { OrdersContainer } from '../../components/orders-container';
import { LedgerContainer } from '../../components/ledger-container';
import { AccountHistoryContainer } from './account-history-container';
@@ -21,6 +20,9 @@ import {
usePaneLayout,
} from '../../components/resizable-grid';
import { ViewType, useSidebar } from '../../components/sidebar';
import { AccountsMenu } from '../../components/accounts-menu';
import { DepositsMenu } from '../../components/deposits-menu';
import { WithdrawalsMenu } from '../../components/withdrawals-menu';
const WithdrawalsIndicator = () => {
const { ready } = useIncompleteWithdrawals();
@@ -51,7 +53,6 @@ export const Portfolio = () => {
}
}, [init, view, setView]);
const onMarketClick = useMarketClickHandler(true);
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
return (
@@ -64,13 +65,13 @@ export const Portfolio = () => {
<AccountHistoryContainer />
</Tab>
<Tab id="positions" name={t('Positions')}>
<PositionsContainer onMarketClick={onMarketClick} allKeys />
<PositionsContainer allKeys />
</Tab>
<Tab id="orders" name={t('Orders')}>
<OrdersContainer />
</Tab>
<Tab id="fills" name={t('Fills')}>
<FillsContainer onMarketClick={onMarketClick} />
<FillsContainer />
</Tab>
<Tab id="ledger-entries" name={t('Ledger entries')}>
<LedgerContainer />
@@ -85,16 +86,21 @@ export const Portfolio = () => {
>
<PortfolioGridChild>
<Tabs storageKey="console-portfolio-bottom">
<Tab id="collateral" name={t('Collateral')}>
<Tab
id="collateral"
name={t('Collateral')}
menu={<AccountsMenu />}
>
<AccountsContainer />
</Tab>
<Tab id="deposits" name={t('Deposits')}>
<Tab id="deposits" name={t('Deposits')} menu={<DepositsMenu />}>
<DepositsContainer />
</Tab>
<Tab
id="withdrawals"
name={t('Withdrawals')}
indicator={<WithdrawalsIndicator />}
menu={<WithdrawalsMenu />}
>
<WithdrawalsContainer />
</Tab>
@@ -1,49 +0,0 @@
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import {
withdrawalProvider,
WithdrawalsTable,
useIncompleteWithdrawals,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { ViewType, useSidebar } from '../../components/sidebar';
export const WithdrawalsContainer = () => {
const { pubKey, isReadOnly } = useVegaWallet();
const { data, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const setView = useSidebar((store) => store.setView);
const { ready, delayed } = useIncompleteWithdrawals();
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<>
<div className="h-full relative">
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
ready={ready}
delayed={delayed}
/>
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end p-2 bottom-0 right-0 absolute dark:bg-black/75 bg-white/75 rounded">
<Button
variant="primary"
size="sm"
onClick={() => setView({ type: ViewType.Withdraw })}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
</Button>
</div>
)}
</>
);
};
@@ -1,5 +1,4 @@
import { useCallback } from 'react';
import { Button } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
@@ -12,16 +11,14 @@ import { useDataGridEvents } from '@vegaprotocol/datagrid';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { ViewType, useSidebar } from '../sidebar';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const AccountsContainer = ({
pinnedAsset,
hideButtons,
onMarketClick,
}: {
pinnedAsset?: PinnedAsset;
hideButtons?: boolean;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
const onMarketClick = useMarketClickHandler(true);
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const setView = useSidebar((store) => store.setView);
@@ -48,44 +45,23 @@ export const AccountsContainer = ({
}
return (
<div className="h-full relative">
<AccountManager
partyId={pubKey}
onClickAsset={onClickAsset}
onClickWithdraw={(assetId) => {
setView({ type: ViewType.Withdraw, assetId });
}}
onClickDeposit={(assetId) => {
setView({ type: ViewType.Deposit, assetId });
}}
onClickTransfer={(assetId) => {
setView({ type: ViewType.Transfer, assetId });
}}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
gridProps={gridStoreCallbacks}
/>
{!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 absolute bottom-0 right-0 dark:bg-black/75 bg-white/75 rounded">
<Button
variant="primary"
size="sm"
data-testid="open-transfer"
onClick={() => setView({ type: ViewType.Transfer })}
>
{t('Transfer')}
</Button>
<Button
variant="primary"
size="sm"
onClick={() => setView({ type: ViewType.Deposit })}
>
{t('Deposit')}
</Button>
</div>
)}
</div>
<AccountManager
partyId={pubKey}
onClickAsset={onClickAsset}
onClickWithdraw={(assetId) => {
setView({ type: ViewType.Withdraw, assetId });
}}
onClickDeposit={(assetId) => {
setView({ type: ViewType.Deposit, assetId });
}}
onClickTransfer={(assetId) => {
setView({ type: ViewType.Transfer, assetId });
}}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
gridProps={gridStoreCallbacks}
/>
);
};
@@ -0,0 +1,27 @@
import { t } from '@vegaprotocol/i18n';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const AccountsMenu = () => {
const setView = useSidebar((store) => store.setView);
return (
<>
<TradingButton
intent={Intent.Primary}
size="extra-small"
data-testid="open-transfer"
onClick={() => setView({ type: ViewType.Transfer })}
>
{t('Transfer')}
</TradingButton>
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Deposit })}
>
{t('Deposit')}
</TradingButton>
</>
);
};
@@ -0,0 +1 @@
export * from './accounts-menu';
@@ -0,0 +1,24 @@
import { Splash } from '@vegaprotocol/ui-toolkit';
import { DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet';
export const DepositsContainer = () => {
const { pubKey } = useVegaWallet();
const { data, error } = useDataProvider({
dataProvider: depositsProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<DepositsTable
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
/>
);
};
@@ -0,0 +1 @@
export * from './deposits-container';
@@ -0,0 +1,18 @@
import { t } from '@vegaprotocol/i18n';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const DepositsMenu = () => {
const setView = useSidebar((store) => store.setView);
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Deposit })}
data-testid="deposit-button"
>
{t('Deposit')}
</TradingButton>
);
};
@@ -0,0 +1 @@
export * from './deposits-menu';
@@ -7,14 +7,10 @@ import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const FillsContainer = ({
marketId,
onMarketClick,
}: {
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
export const FillsContainer = ({ marketId }: { marketId?: string }) => {
const onMarketClick = useMarketClickHandler(true);
const { pubKey } = useVegaWallet();
const gridStore = useFillsStore((store) => store.gridStore);
@@ -134,6 +134,20 @@ describe('MarketSelector', () => {
error: undefined,
});
it('Button "All" should be selected by default', () => {
const buttons = ['All', 'Futures', 'Spot', 'Perpetuals'];
render(
<MemoryRouter>
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
</MemoryRouter>
);
screen
.getAllByTestId(/^product-(All|Future|Spot|Perpetual)$/)
.forEach((elem, i) => {
expect(elem.textContent).toEqual(buttons[i]);
});
});
it('renders only active markets', () => {
render(
<MemoryRouter>
@@ -169,6 +183,12 @@ describe('MarketSelector', () => {
activeMarkets.length
);
expect(screen.queryByTestId('no-items')).not.toBeInTheDocument();
await userEvent.click(screen.getByTestId('product-All'));
expect(screen.queryAllByTestId(/market-\d/)).toHaveLength(
activeMarkets.length
);
expect(screen.queryByTestId('no-items')).not.toBeInTheDocument();
});
it('filters by search term', async () => {
@@ -39,7 +39,7 @@ export const MarketSelector = ({
}) => {
const [filter, setFilter] = useState<Filter>({
searchTerm: '',
product: Product.Future,
product: Product.All,
sort: Sort.None,
assets: [],
});
@@ -6,6 +6,7 @@ import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
// Make sure these match the available __typename properties on product
export const Product = {
All: 'All',
Future: 'Future',
Spot: 'Spot',
Perpetual: 'Perpetual',
@@ -16,6 +17,7 @@ export type ProductType = keyof typeof Product;
const ProductTypeMapping: {
[key in ProductType]: string;
} = {
[Product.All]: 'All',
[Product.Future]: 'Futures',
[Product.Spot]: 'Spot',
[Product.Perpetual]: 'Perpetuals',
@@ -49,8 +51,12 @@ export const ProductSelector = ({
</button>
);
})}
<Link to={Routes.MARKETS} className="flex items-center gap-2 ml-auto">
<span className="underline underline-offset-4">{t('All markets')}</span>
<Link
to={Routes.MARKETS}
className="flex items-center gap-2 ml-auto"
title={t('See all markets')}
>
<span className="underline underline-offset-4">{t('Browse')}</span>
<VegaIcon name={VegaIconNames.ARROW_RIGHT} />
</Link>
</div>
@@ -120,6 +120,13 @@ describe('useMarketSelectorList', () => {
assets: [],
});
expect(result.current.markets).toEqual([markets[2]]);
rerender({
searchTerm: '',
product: Product.All,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual(markets);
});
it('filters by asset', () => {
@@ -5,6 +5,7 @@ import { calcCandleVolume, useMarketList } from '@vegaprotocol/markets';
import { priceChangePercentage } from '@vegaprotocol/utils';
import type { Filter } from '../../components/market-selector/market-selector';
import { Sort } from './sort-dropdown';
import { Product } from './product-selector';
// Used for sort order and filter
const MARKET_TEMPLATE = [
@@ -28,7 +29,10 @@ export const useMarketSelectorList = ({
.filter((m) => isMarketActive(m.state))
// only selected product type
.filter((m) => {
if (m.tradableInstrument.instrument.product.__typename === product) {
if (
product === Product.All ||
m.tradableInstrument.instrument.product.__typename === product
) {
return true;
}
return false;
@@ -25,11 +25,10 @@ export const FilterStatusValue = {
};
export interface OrderContainerProps {
marketId?: string;
filter?: Filter;
}
export const OrdersContainer = ({ marketId, filter }: OrderContainerProps) => {
export const OrdersContainer = ({ filter }: OrderContainerProps) => {
const { pubKey, isReadOnly } = useVegaWallet();
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
@@ -45,7 +44,6 @@ export const OrdersContainer = ({ marketId, filter }: OrderContainerProps) => {
return (
<OrderListManager
partyId={pubKey}
marketId={marketId}
filter={filter}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
@@ -7,14 +7,10 @@ import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const PositionsContainer = ({
onMarketClick,
allKeys,
}: {
onMarketClick?: (marketId: string) => void;
allKeys?: boolean;
}) => {
export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
const onMarketClick = useMarketClickHandler(true);
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
const gridStore = usePositionsStore((store) => store.gridStore);
@@ -0,0 +1 @@
export * from './withdrawals-container';
@@ -0,0 +1,31 @@
import { Splash } from '@vegaprotocol/ui-toolkit';
import {
withdrawalProvider,
WithdrawalsTable,
useIncompleteWithdrawals,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
export const WithdrawalsContainer = () => {
const { pubKey } = useVegaWallet();
const { data, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const { ready, delayed } = useIncompleteWithdrawals();
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
ready={ready}
delayed={delayed}
/>
);
};
@@ -0,0 +1 @@
export * from './withdrawals-menu';
@@ -0,0 +1,18 @@
import { t } from '@vegaprotocol/i18n';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const WithdrawalsMenu = () => {
const setView = useSidebar((store) => store.setView);
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Withdraw })}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
</TradingButton>
);
};
-3
View File
@@ -1,11 +1,9 @@
import {
RestConnector,
JsonRpcConnector,
ViewConnector,
InjectedConnector,
} from '@vegaprotocol/wallet';
export const rest = new RestConnector();
export const jsonRpc = new JsonRpcConnector();
export const injected = new InjectedConnector();
@@ -19,7 +17,6 @@ if (typeof window !== 'undefined') {
export const Connectors = {
injected,
rest,
jsonRpc,
view,
};
+27 -1
View File
@@ -1,3 +1,4 @@
import Head from 'next/head';
import { ClientRouter } from './client-router';
/**
@@ -6,5 +7,30 @@ import { ClientRouter } from './client-router';
* have to serve a static site via next export
*/
export default function Index() {
return <ClientRouter />;
return (
<>
<Head>
<meta charSet="utf-8" />
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Vega Protocol - Console" />
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
<link rel="manifest" href="assets/manifest.json" />
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
/>
<title>VEGA Console dApp</title>
</Head>
<ClientRouter />
</>
);
}
@@ -10,14 +10,11 @@ declare global {
export const addConnectPublicKey = () => {
Cypress.Commands.add('connectPublicKey', (publicKey) => {
const connectVegaWaletBtn = Cypress.$(
`[data-testid="connect-vega-wallet"]`
);
const connectVegaWaletBtn = Cypress.$(`[data-testid="view-as-user"]`);
if (connectVegaWaletBtn.length > 0) {
cy.get('aside [data-testid="connect-vega-wallet"]').click();
cy.getByTestId('connector-view').should('be.visible').click();
cy.getByTestId('address').click();
cy.getByTestId('address').type(publicKey);
cy.get('aside button').contains('View as party').click();
cy.getByTestId('address').should('be.visible').focus();
cy.getByTestId('address').type(publicKey, { delay: 50 });
cy.getByTestId('connect').click();
}
});
@@ -29,7 +29,7 @@ export const mockConnectWallet = () => {
export const mockConnectWalletWithUserError = () => {
cy.mockWallet((req) => {
aliasWalletConnectWithUserError(req);
aliasWalletConnectWithUserError(req, Cypress.env('VEGA_WALLET_API_TOKEN'));
});
};
+34 -1
View File
@@ -62,10 +62,27 @@ export const aliasWalletConnectQuery = (
},
});
}
if (hasMethod(req, 'client.get_chain_id')) {
req.reply({
statusCode: 200,
headers: {
'Access-Control-Expose-Headers': 'Authorization',
Authorization: `VWT ${token}`,
},
body: {
jsonrpc: '2.0',
result: {
chainID: 'test-id',
},
id: '1',
},
});
}
};
export const aliasWalletConnectWithUserError = (
req: CyHttpMessages.IncomingHttpRequest
req: CyHttpMessages.IncomingHttpRequest,
token: string
) => {
if (hasMethod(req, 'client.connect_wallet')) {
req.alias = 'client.connect_wallet';
@@ -82,4 +99,20 @@ export const aliasWalletConnectWithUserError = (
},
});
}
if (hasMethod(req, 'client.get_chain_id')) {
req.reply({
statusCode: 200,
headers: {
'Access-Control-Expose-Headers': 'Authorization',
Authorization: `VWT ${token}`,
},
body: {
jsonrpc: '2.0',
result: {
chainID: 'test-id',
},
id: '1',
},
});
}
};
@@ -1,4 +1,4 @@
import { Intent, Notification, Link } from '@vegaprotocol/ui-toolkit';
import { Intent, Notification } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
interface ZeroBalanceErrorProps {
@@ -6,13 +6,11 @@ interface ZeroBalanceErrorProps {
id: string;
symbol: string;
};
onClickCollateral?: () => void;
onDeposit: (assetId: string) => void;
}
export const ZeroBalanceError = ({
asset,
onClickCollateral,
onDeposit,
}: ZeroBalanceErrorProps) => {
return (
@@ -25,12 +23,6 @@ export const ZeroBalanceError = ({
'You need %s in your wallet to trade in this market. ',
asset.symbol
)}
{onClickCollateral && (
<>
{t('See all your')}{' '}
<Link onClick={onClickCollateral}>collateral</Link>.
</>
)}
</>
}
buttonProps={{
@@ -17,7 +17,6 @@ import { FLAGS } from '@vegaprotocol/environment';
interface DealTicketContainerProps {
marketId: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
onClickCollateral?: () => void;
onDeposit: (assetId: string) => void;
}
@@ -74,7 +74,6 @@ export interface DealTicketProps {
marketPrice?: string | null;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
submit: (order: OrderSubmission) => void;
onClickCollateral?: () => void;
onDeposit: (assetId: string) => void;
}
@@ -121,7 +120,6 @@ export const DealTicket = ({
marketData,
marketPrice,
submit,
onClickCollateral,
onDeposit,
}: DealTicketProps) => {
const { pubKey, isReadOnly } = useVegaWallet();
@@ -530,7 +528,6 @@ export const DealTicket = ({
}
isReadOnly={isReadOnly}
pubKey={pubKey}
onClickCollateral={onClickCollateral}
onDeposit={onDeposit}
/>
<DealTicketButton side={side} />
@@ -566,7 +563,6 @@ interface SummaryMessageProps {
margin: string;
isReadOnly: boolean;
pubKey: string | null;
onClickCollateral?: () => void;
onDeposit: (assetId: string) => void;
}
@@ -596,7 +592,6 @@ const SummaryMessage = memo(
margin,
isReadOnly,
pubKey,
onClickCollateral,
onDeposit,
}: SummaryMessageProps) => {
// Specific error UI for if balance is so we can
@@ -608,11 +603,7 @@ const SummaryMessage = memo(
if (error?.type === SummaryValidationType.NoCollateral) {
return (
<div className="mb-2">
<ZeroBalanceError
asset={asset}
onClickCollateral={onClickCollateral}
onDeposit={onDeposit}
/>
<ZeroBalanceError asset={asset} onDeposit={onDeposit} />
</div>
);
}
@@ -371,6 +371,14 @@ function compileEnvVars() {
'NX_TENDERMINT_WEBSOCKET_URL',
process.env['NX_TENDERMINT_WEBSOCKET_URL']
),
CHROME_EXTENSION_URL: windowOrDefault(
'NX_CHROME_EXTENSION_URL',
process.env['NX_CHROME_EXTENSION_URL']
),
MOZILLA_EXTENSION_URL: windowOrDefault(
'NX_MOZILLA_EXTENSION_URL',
process.env['NX_MOZILLA_EXTENSION_URL']
),
};
return env;
+2
View File
@@ -160,7 +160,9 @@ export const ExternalLinks = {
MARGIN_CREDIT_RISK:
'https://vega.xyz/papers/margins-and-credit-risk.pdf#page=7',
VEGA_WALLET_URL: 'https://vega.xyz/wallet',
VEGA_WALLET_URL_ABOUT: 'https://vega.xyz/wallet/#overview',
VEGA_WALLET_HOSTED_URL: 'https://vega-hosted-wallet.on.fleek.co/',
VEGA_WALLET_BROWSER_LIST: '',
BLOG: 'https://blog.vega.xyz/',
};
@@ -59,6 +59,8 @@ export const envSchema = z
SENTRY_DSN: z.optional(z.string()),
TENDERMINT_URL: z.optional(z.string()),
TENDERMINT_WEBSOCKET_URL: z.optional(z.string()),
CHROME_EXTENSION_URL: z.optional(z.string()),
MOZILLA_EXTENSION_URL: z.optional(z.string()),
})
.refine(
(data) => {
+1
View File
@@ -1,3 +1,4 @@
export * from './open-orders-menu';
export * from './order-data-provider';
export * from './order-list';
export * from './order-list-manager';
@@ -0,0 +1 @@
export * from './open-orders-menu';
@@ -0,0 +1,39 @@
import { t } from '@vegaprotocol/i18n';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { useVegaTransactionStore, useVegaWallet } from '@vegaprotocol/wallet';
import { useHasAmendableOrder } from '../../order-hooks';
export const OpenOrdersMenu = ({ marketId }: { marketId: string }) => {
const { isReadOnly } = useVegaWallet();
const create = useVegaTransactionStore((state) => state.create);
const hasAmendableOrder = useHasAmendableOrder(marketId);
if (isReadOnly) {
return null;
}
if (!hasAmendableOrder) {
return null;
}
return (
<CancelAllOrdersButton
onClick={() => {
create({
orderCancellation: {},
});
}}
/>
);
};
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={onClick}
data-testid="cancelAll"
>
{t('Cancel all')}
</TradingButton>
);
@@ -1,9 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import { useCallback, useRef, useState } from 'react';
import { Button } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { OrderListTable } from '../order-list/order-list';
import { useHasAmendableOrder } from '../../order-hooks/use-has-amendable-order';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
@@ -24,7 +22,6 @@ export enum Filter {
export interface OrderListManagerProps {
partyId: string;
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
isReadOnly: boolean;
@@ -34,7 +31,6 @@ export interface OrderListManagerProps {
export const OrderListManager = ({
partyId,
marketId,
onMarketClick,
onOrderTypeClick,
isReadOnly,
@@ -45,7 +41,6 @@ export const OrderListManager = ({
const [editOrder, setEditOrder] = useState<Order | null>(null);
const [viewOrder, setViewOrder] = useState<Order | null>(null);
const create = useVegaTransactionStore((state) => state.create);
const hasAmendableOrder = useHasAmendableOrder(marketId);
const variables =
filter === Filter.Open
? { partyId, filter: { liveOnly: true } }
@@ -76,12 +71,6 @@ export const OrderListManager = ({
[create]
);
const cancelAll = useCallback(() => {
create({
orderCancellation: {},
});
}, [create]);
return (
<>
<div className="h-full relative">
@@ -100,9 +89,6 @@ export const OrderListManager = ({
{...gridProps}
/>
</div>
{!isReadOnly && hasAmendableOrder && (
<CancelAllOrdersButton onClick={cancelAll} />
)}
{editOrder && (
<OrderEditDialog
isOpen={Boolean(editOrder)}
@@ -148,16 +134,3 @@ export const OrderListManager = ({
</>
);
};
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
<div className="dark:bg-black/75 bg-white/75 h-auto flex justify-end p-2 absolute bottom-0 right-0 rounded">
<Button
variant="primary"
size="sm"
onClick={onClick}
data-testid="cancelAll"
>
{t('Cancel all')}
</Button>
</div>
);
@@ -18,6 +18,7 @@ import {
import type { Order } from '../order-data-provider';
import CopyToClipboard from 'react-copy-to-clipboard';
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
import classNames from 'classnames';
interface OrderViewDialogProps {
isOpen: boolean;
@@ -50,6 +51,21 @@ export const OrderViewDialog = ({
)}
</div>
</KeyValueTableRow>
<KeyValueTableRow key={'order-side'}>
<div data-testid={'order-side-label'}>{t('Side')}</div>
<div
data-testid={`order-side-value`}
className={classNames({
// BUY
'text-market-green-600 dark:text-market-green':
order.side === Schema.Side.SIDE_BUY,
// SELL
'text-market-red': order.side === Schema.Side.SIDE_SELL,
})}
>
{Schema.SideMapping[order.side as Schema.Side]}
</div>
</KeyValueTableRow>
<KeyValueTableRow key={'order-type'}>
<div data-testid={'order-type-label'}>{t('Type')}</div>
<div data-testid={`order-type-value`}>
@@ -223,7 +239,6 @@ export const OrderViewDialog = ({
<div data-testid={`order-iceberg-order-peak-size-value`}>
<Size
value={order.icebergOrder.peakSize}
side={order.side}
positionDecimalPlaces={
order.market?.positionDecimalPlaces as number
}
@@ -248,7 +263,6 @@ export const OrderViewDialog = ({
<div data-testid={`order-iceberg-order-minimum-visible-size-value`}>
<Size
value={order.icebergOrder.minimumVisibleSize}
side={order.side}
positionDecimalPlaces={
order.market?.positionDecimalPlaces as number
}
@@ -267,7 +281,6 @@ export const OrderViewDialog = ({
<div data-testid={`order-iceberg-order-reserved-remaining-value`}>
<Size
value={order.icebergOrder.reservedRemaining}
side={order.side}
positionDecimalPlaces={
order.market?.positionDecimalPlaces as number
}
@@ -0,0 +1,19 @@
export const IconArrowLeft = ({ size = 16 }: { size: number }) => {
return (
<svg width={size} height={size} viewBox="0 0 16 16">
<path
d="M 3.63,7.47
C 3.63,7.47 8.72,2.37 8.72,2.37
8.72,2.37 7.98,1.63 7.98,1.63
7.98,1.63 1.60,8.00 1.60,8.00
1.60,8.00 7.98,14.37 7.98,14.37
7.98,14.37 8.72,13.63 8.72,13.63
8.72,13.63 3.63,8.53 3.63,8.53
3.63,8.53 14.35,8.53 14.35,8.53
14.35,8.53 14.35,7.47 14.35,7.47
14.35,7.47 3.63,7.47 3.63,7.47 Z
M 35.00,44.00"
/>
</svg>
);
};
@@ -0,0 +1,19 @@
export const IconArrowTopRight = ({ size = 16 }: { size: number }) => {
return (
<svg width={size} height={size} viewBox="0 0 14 14">
<path
d="M 10.47,4.28
C 10.47,4.28 10.47,11.49 10.47,11.49
10.47,11.49 11.53,11.49 11.53,11.49
11.53,11.49 11.53,2.47 11.53,2.47
11.53,2.47 2.51,2.47 2.51,2.47
2.51,2.47 2.51,3.53 2.51,3.53
2.51,3.53 9.72,3.53 9.72,3.53
9.72,3.53 2.14,11.12 2.14,11.12
2.14,11.12 2.88,11.86 2.88,11.86
2.88,11.86 10.47,4.28 10.47,4.28 Z
M -37.55,0.64"
/>
</svg>
);
};
@@ -1,6 +1,8 @@
import { IconArrowDown } from './svg-icons/icon-arrow-down';
import { IconArrowLeft } from './svg-icons/icon-arrow-left';
import { IconArrowUp } from './svg-icons/icon-arrow-up';
import { IconArrowRight } from './svg-icons/icon-arrow-right';
import { IconArrowTopRight } from './svg-icons/icon-arrow-top-right';
import { IconBreakdown } from './svg-icons/icon-breakdown';
import { IconBullet } from './svg-icons/icon-bullet';
import { IconChevronDown } from './svg-icons/icon-chevron-down';
@@ -36,8 +38,10 @@ import { IconSearch } from './svg-icons/icon-search';
export enum VegaIconNames {
ARROW_DOWN = 'arrow-down',
ARROW_LEFT = 'arrow-left',
ARROW_UP = 'arrow-up',
ARROW_RIGHT = 'arrow-right',
ARROW_TOP_RIGHT = 'arrow-top-right',
BREAKDOWN = 'breakdown',
BULLET = 'bullet',
CHEVRON_DOWN = 'chevron-down',
@@ -77,8 +81,10 @@ export const VegaIconNameMap: Record<
({ size }: { size: number }) => JSX.Element
> = {
'arrow-down': IconArrowDown,
'arrow-left': IconArrowLeft,
'arrow-up': IconArrowUp,
'arrow-right': IconArrowRight,
'arrow-top-right': IconArrowTopRight,
breakdown: IconBreakdown,
bullet: IconBullet,
'chevron-down': IconChevronDown,
+1
View File
@@ -54,3 +54,4 @@ export * from './traffic-light';
export * from './vega-icons';
export * from './vega-logo';
export * from './viewing-as-user';
export * from './pill';
@@ -0,0 +1 @@
export * from './pill';
@@ -0,0 +1,48 @@
import type { Story, Meta } from '@storybook/react';
import { Pill } from './pill';
import { Intent } from '../../utils/intent';
export default {
component: Pill,
title: 'Pill',
} as Meta;
const Template: Story = (args) => <Pill {...args}>Pill</Pill>;
export const Default = Template.bind({ intent: Intent.Primary, size: 'md' });
export const None = Template.bind({});
None.args = {
intent: Intent.None,
size: 'md',
};
export const Info = Template.bind({});
Info.args = {
intent: Intent.Info,
size: 'md',
};
export const Primary = Template.bind({});
Primary.args = {
intent: Intent.Primary,
size: 'md',
};
export const Success = Template.bind({});
Success.args = {
intent: Intent.Success,
size: 'md',
};
export const Warning = Template.bind({});
Warning.args = {
intent: Intent.Warning,
size: 'md',
};
export const Danger = Template.bind({});
Danger.args = {
intent: Intent.Danger,
size: 'md',
};
@@ -0,0 +1,44 @@
import type { ReactNode } from 'react';
import { Intent } from '../../utils/intent';
import classNames from 'classnames';
type Size = 'lg' | 'md' | 'sm' | 'xs' | 'xxs';
interface Props {
children: ReactNode;
intent?: Intent;
size?: Size;
className?: string;
}
const getClasses = (size: Size, intent?: Intent, className?: string) => {
return classNames(
['rounded-md', 'leading-none', 'font-alpha', 'py-1 px-2'],
{
'bg-vega-yellow dark:bg-vega-yellow': intent === Intent.Primary,
'bg-vega-clight-500 dark:bg-vega-cdark-500': intent === Intent.None,
'bg-vega-blue-500 dark:bg-vega-blue-500': intent === Intent.Info,
'bg-vega-orange-350 dark:bg-vega-orange-650': intent === Intent.Warning,
'bg-vega-red-350 dark:bg-vega-red-650': intent === Intent.Danger,
'bg-vega-green-350 dark:bg-vega-green-650': intent === Intent.Success,
'text-vega-clight-50 dark:text-vega-cdark-50': intent !== Intent.Primary,
'text-vega-clight-900 dark:text-vega-cdark-900':
intent === Intent.Primary,
},
{
'text-lg': size === 'lg',
'text-base': size === 'md',
'text-sma': size === 'sm',
'text-xs': size === 'xs',
'text-[10px]': size === 'xxs',
},
className
);
};
export const Pill = ({ intent, size, className, children }: Props) => {
return (
<span className={getClasses(size || 'md', intent, className)}>
{children}
</span>
);
};
@@ -13,6 +13,7 @@ type TradingButtonProps = {
children?: ReactNode;
icon?: ReactNode;
subLabel?: ReactNode;
fill?: boolean;
};
const getClassName = (
@@ -20,7 +21,8 @@ const getClassName = (
size,
subLabel,
intent,
}: Pick<TradingButtonProps, 'size' | 'subLabel' | 'intent'>,
fill,
}: Pick<TradingButtonProps, 'size' | 'subLabel' | 'intent' | 'fill'>,
className?: string
) =>
classNames(
@@ -61,6 +63,7 @@ const getClassName = (
intent === Intent.Primary,
'[&_[data-sub-label]]:text-vega-clight-100': intent === Intent.Primary,
},
{ 'w-full': fill },
className
);
@@ -99,6 +102,7 @@ export const TradingButton = forwardRef<
children,
className,
subLabel,
fill,
...props
},
ref
@@ -107,7 +111,7 @@ export const TradingButton = forwardRef<
ref={ref}
type={type}
data-trading-button
className={getClassName({ size, subLabel, intent }, className)}
className={getClassName({ size, subLabel, intent, fill }, className)}
{...props}
>
<Content icon={icon} subLabel={subLabel} children={children} />
@@ -1,16 +1,18 @@
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
import { ExternalLinks, useEnvironment } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import { Link } from '@vegaprotocol/ui-toolkit';
import {
ExternalLink,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { ReactNode } from 'react';
import type { VegaConnector } from '../connectors';
import { RestConnector } from '../connectors';
export const ConnectDialogTitle = ({ children }: { children: ReactNode }) => {
return (
<h1
data-testid="wallet-dialog-title"
className="text-2xl uppercase mb-6 text-center font-alpha calt"
className="text-2xl uppercase mb-6 font-alpha calt"
>
{children}
</h1>
@@ -21,48 +23,353 @@ export const ConnectDialogContent = ({ children }: { children: ReactNode }) => {
return <div>{children}</div>;
};
export const ConnectDialogFooter = ({
connector,
}: {
connector: VegaConnector | undefined;
}) => {
export const ConnectDialogFooter = () => {
const wrapperClasses = classNames(
'flex justify-center gap-4',
'flex justify-center gap-4 mt-4',
'px-4 md:px-8 pt-4 md:pt-6',
'border-t border-vega-light-200 dark:border-vega-dark-200',
'text-vega-light-400 dark:text-vega-dark-400'
'text-vega-light-400 dark:text-vega-dark-400 text-sm'
);
const isHostedWalletSelected = connector instanceof RestConnector;
return (
<footer className={wrapperClasses}>
{isHostedWalletSelected ? (
<p className="text-center">
{t('For demo purposes get a ')}
<Link
href={ExternalLinks.VEGA_WALLET_HOSTED_URL}
target="_blank"
rel="noreferrer"
>
{t('hosted wallet')}
</Link>
{t(', or for the real experience create a wallet in the ')}
<Link href={ExternalLinks.VEGA_WALLET_URL}>
{t('Vega wallet app')}
</Link>
</p>
) : (
<ExternalLink
href={ExternalLinks.VEGA_WALLET_URL_ABOUT}
className="underline"
>
{t('About the Vega wallet')}{' '}
<VegaIcon name={VegaIconNames.ARROW_TOP_RIGHT} />
</ExternalLink>
{ExternalLinks.VEGA_WALLET_BROWSER_LIST && (
<>
<Link href={ExternalLinks.VEGA_WALLET_URL}>
{t('Get a Vega Wallet')}
</Link>
{' | '}
{DocsLinks && (
<Link href={DocsLinks.VEGA_WALLET_CONCEPTS_URL}>
{t('Having trouble?')}
</Link>
)}
<ExternalLink
href={ExternalLinks.VEGA_WALLET_BROWSER_LIST}
className="underline"
>
{t('Supported browsers')}{' '}
<VegaIcon name={VegaIconNames.ARROW_TOP_RIGHT} />
</ExternalLink>
</>
)}
</footer>
);
};
export const ChromeIcon = () => {
return (
<svg
width="28"
height="28"
viewBox="0 0 32 32"
fill="none"
data-testid="chrome-logo"
>
<g clipPath="url(#clip0_3681_24659)">
<path
d="M15.9987 9.99963L26.3893 9.99964C25.3364 8.17534 23.8217 6.6604 21.9976 5.60716C20.1735 4.55391 18.1042 3.99949 15.9979 3.99964C13.8915 3.99979 11.8223 4.5545 9.99837 5.608C8.1744 6.66149 6.65995 8.17664 5.6073 10.0011L10.8026 18.9996L10.8072 18.9984C10.2787 18.0871 9.99984 17.0525 9.99865 15.999C9.99747 14.9454 10.274 13.9102 10.8005 12.9977C11.3269 12.0851 12.0847 11.3275 12.9973 10.8011C13.9099 10.2748 14.9451 9.99832 15.9987 9.99963Z"
fill="url(#paint0_linear_3681_24659)"
/>
<path
d="M21.1974 18.9989L16.0021 27.9974C18.1084 27.9977 20.1777 27.4435 22.0019 26.3904C23.8261 25.3373 25.3409 23.8224 26.3939 21.9982C27.447 20.174 28.0012 18.1047 28.0008 15.9983C28.0004 13.892 27.4455 11.8228 26.3918 9.99898L16.0012 9.99899L15.9999 10.0036C17.0534 10.0016 18.0889 10.2773 19.0018 10.8031C19.9148 11.3288 20.673 12.0859 21.2001 12.9981C21.7272 13.9103 22.0044 14.9454 22.004 15.9989C22.0035 17.0524 21.7253 18.0872 21.1974 18.9989Z"
fill="url(#paint1_linear_3681_24659)"
/>
<path
d="M10.8044 19.0016L5.60914 10.0031C4.5557 11.8271 4.00106 13.8963 4.00098 16.0026C4.0009 18.109 4.55539 20.1782 5.60869 22.0023C6.66199 23.8264 8.17698 25.341 10.0013 26.3938C11.8257 27.4467 13.895 28.0007 16.0014 28.0001L21.1967 19.0015L21.1933 18.9981C20.6683 19.9115 19.9118 20.6703 19 21.1981C18.0882 21.7259 17.0534 22.004 15.9999 22.0043C14.9464 22.0047 13.9114 21.7273 12.9992 21.2001C12.0871 20.6729 11.3301 19.9146 10.8044 19.0016Z"
fill="url(#paint2_linear_3681_24659)"
/>
<path
d="M16 22C19.3137 22 22 19.3137 22 16C22 12.6863 19.3137 10 16 10C12.6863 10 10 12.6863 10 16C10 19.3137 12.6863 22 16 22Z"
fill="white"
/>
<path
d="M16 20.75C18.6234 20.75 20.75 18.6234 20.75 16C20.75 13.3766 18.6234 11.25 16 11.25C13.3766 11.25 11.25 13.3766 11.25 16C11.25 18.6234 13.3766 20.75 16 20.75Z"
fill="#1A73E8"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_3681_24659"
x1="25.093"
y1="9.25084"
x2="14.702"
y2="27.2485"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#D93025" />
<stop offset="1" stopColor="#EA4335" />
</linearGradient>
<linearGradient
id="paint1_linear_3681_24659"
x1="27.073"
y1="11.4997"
x2="6.29104"
y2="11.4997"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FCC934" />
<stop offset="1" stopColor="#FBBC04" />
</linearGradient>
<linearGradient
id="paint2_linear_3681_24659"
x1="17.2992"
y1="27.2508"
x2="6.90819"
y2="9.25305"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#1E8E3E" />
<stop offset="1" stopColor="#34A853" />
</linearGradient>
<clipPath id="clip0_3681_24659">
<rect
width="24"
height="24"
fill="white"
transform="translate(4 4)"
/>
</clipPath>
</defs>
</svg>
);
};
export const MozillaIcon = () => {
return (
<svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
data-testid="mozilla-logo"
>
<g clipPath="url(#clip0_3681_24667)">
<path
d="M22.4398 7.79786C21.9502 6.62017 20.9585 5.34873 20.1798 4.94687C20.8136 6.1893 21.1804 7.43561 21.3205 8.36575C21.3205 8.36758 21.3212 8.37212 21.3227 8.3845C20.0489 5.20951 17.889 3.92926 16.1252 1.1417C16.0361 1.00075 15.9468 0.85942 15.8598 0.710452C15.8155 0.634372 15.7742 0.556628 15.7358 0.477389C15.6625 0.335913 15.606 0.186371 15.5674 0.0317953C15.5676 0.0244774 15.5652 0.017323 15.5604 0.0117374C15.5557 0.00615177 15.549 0.00253868 15.5418 0.00160783C15.5349 -0.000373182 15.5275 -0.000373182 15.5206 0.00160783C15.519 0.00217033 15.5167 0.00399845 15.515 0.0046547C15.5125 0.00563908 15.5094 0.00788908 15.5068 0.0093422C15.508 0.0076547 15.5107 0.00385783 15.5115 0.0029672C12.6816 1.66028 11.7216 4.72614 11.6334 6.26003C10.5034 6.33772 9.42293 6.75406 8.53298 7.45478C8.43981 7.37608 8.3424 7.30253 8.24119 7.23447C7.98442 6.33615 7.97351 5.38538 8.20959 4.4814C7.05234 5.00833 6.15225 5.84125 5.49787 6.57672H5.49267C5.04609 6.01108 5.07759 4.14517 5.10305 3.75559C5.0977 3.73145 4.76991 3.92575 4.72697 3.95505C4.3329 4.23633 3.96449 4.55194 3.62606 4.89817C3.24095 5.28871 2.88907 5.71069 2.57409 6.15972C2.57409 6.16028 2.57377 6.16094 2.57358 6.1615C2.57358 6.16089 2.57391 6.16028 2.57409 6.15972C1.8497 7.18625 1.33595 8.34618 1.06252 9.57245C1.05712 9.59687 1.05258 9.62219 1.04733 9.6468C1.02614 9.74598 0.949828 10.2421 0.936469 10.3499C0.935438 10.3582 0.934969 10.3662 0.933984 10.3745C0.835324 10.8874 0.774224 11.4069 0.751172 11.9287C0.751172 11.9479 0.75 11.967 0.75 11.9862C0.750187 18.2072 5.79394 23.2501 12.0154 23.2501C17.5872 23.2501 22.2135 19.2053 23.1192 13.8924C23.1383 13.7482 23.1536 13.6033 23.1704 13.4578C23.3943 11.5261 23.1456 9.49572 22.4398 7.79786ZM9.45562 16.6148C9.50831 16.6399 9.55781 16.6675 9.61191 16.6916C9.61416 16.6931 9.61725 16.6949 9.61955 16.6963C9.56449 16.67 9.50984 16.6428 9.45562 16.6148ZM21.3236 8.38726L21.3221 8.37634C21.3227 8.38033 21.3234 8.3845 21.324 8.38848L21.3236 8.38726Z"
fill="url(#paint0_linear_3681_24667)"
/>
<path
d="M22.4397 7.79776C21.9501 6.62007 20.9584 5.34864 20.1797 4.94678C20.8135 6.1892 21.1803 7.43551 21.3204 8.36565C21.3204 8.36293 21.321 8.3679 21.3221 8.37625C21.3227 8.38023 21.3234 8.3844 21.324 8.38839C22.3869 11.2698 21.8078 14.1999 20.9734 15.9903C19.6825 18.7607 16.5571 21.5999 11.6652 21.4614C6.37978 21.3117 1.7235 17.39 0.854297 12.2536C0.695906 11.4436 0.854297 11.0323 0.933984 10.3746C0.836906 10.8816 0.799922 11.0281 0.751172 11.9289C0.751172 11.9481 0.75 11.9671 0.75 11.9864C0.750094 18.2071 5.79384 23.25 12.0153 23.25C17.5871 23.25 22.2134 19.2052 23.1191 13.8923C23.1382 13.7481 23.1535 13.6032 23.1703 13.4577C23.3942 11.526 23.1455 9.49562 22.4397 7.79776Z"
fill="url(#paint1_radial_3681_24667)"
/>
<path
d="M22.4397 7.79776C21.9501 6.62007 20.9584 5.34864 20.1797 4.94678C20.8135 6.1892 21.1803 7.43551 21.3204 8.36565C21.3204 8.36293 21.321 8.3679 21.3221 8.37625C21.3227 8.38023 21.3234 8.3844 21.324 8.38839C22.3869 11.2698 21.8078 14.1999 20.9734 15.9903C19.6825 18.7607 16.5571 21.5999 11.6652 21.4614C6.37978 21.3117 1.7235 17.39 0.854297 12.2536C0.695906 11.4436 0.854297 11.0323 0.933984 10.3746C0.836906 10.8816 0.799922 11.0281 0.751172 11.9289C0.751172 11.9481 0.75 11.9671 0.75 11.9864C0.750094 18.2071 5.79384 23.25 12.0153 23.25C17.5871 23.25 22.2134 19.2052 23.1191 13.8923C23.1382 13.7481 23.1535 13.6032 23.1703 13.4577C23.3942 11.526 23.1455 9.49562 22.4397 7.79776Z"
fill="url(#paint2_radial_3681_24667)"
/>
<path
d="M16.965 9.12184C16.9896 9.13909 17.0119 9.15625 17.035 9.1734C16.7523 8.67164 16.4002 8.21224 15.9892 7.80878C12.4874 4.3074 15.071 0.216811 15.5067 0.00906055C15.5079 0.00737305 15.5106 0.00357617 15.5114 0.00268555C12.6815 1.66 11.7215 4.72586 11.6333 6.25975C11.7646 6.25065 11.8954 6.23964 12.029 6.23964C14.1408 6.23964 15.9801 7.40073 16.965 9.12184Z"
fill="url(#paint3_radial_3681_24667)"
/>
<path
d="M12.0361 9.82097C12.0176 10.1012 11.0276 11.0675 10.6814 11.0675C7.47799 11.0675 6.95801 13.0051 6.95801 13.0051C7.0999 14.6368 8.23587 15.9805 9.61165 16.6915C9.67441 16.7239 9.73793 16.7532 9.80149 16.7822C9.91047 16.8304 10.0208 16.8756 10.1324 16.9175C10.6041 17.0845 11.0982 17.1798 11.5982 17.2002C17.2129 17.4635 18.3007 10.488 14.2488 8.4623C15.2864 8.28183 16.3635 8.69916 16.965 9.12169C15.9801 7.40072 14.1408 6.23962 12.0291 6.23962C11.8955 6.23962 11.7647 6.25064 11.6334 6.25973C10.5033 6.33742 9.42291 6.75376 8.53296 7.45448C8.70471 7.5998 8.89859 7.79405 9.30705 8.19642C10.0712 8.94956 12.0319 9.72947 12.0361 9.82097Z"
fill="url(#paint4_radial_3681_24667)"
/>
<path
d="M12.0361 9.82097C12.0176 10.1012 11.0276 11.0675 10.6814 11.0675C7.47799 11.0675 6.95801 13.0051 6.95801 13.0051C7.0999 14.6368 8.23587 15.9805 9.61165 16.6915C9.67441 16.7239 9.73793 16.7532 9.80149 16.7822C9.91047 16.8304 10.0208 16.8756 10.1324 16.9175C10.6041 17.0845 11.0982 17.1798 11.5982 17.2002C17.2129 17.4635 18.3007 10.488 14.2488 8.4623C15.2864 8.28183 16.3635 8.69916 16.965 9.12169C15.9801 7.40072 14.1408 6.23962 12.0291 6.23962C11.8955 6.23962 11.7647 6.25064 11.6334 6.25973C10.5033 6.33742 9.42291 6.75376 8.53296 7.45448C8.70471 7.5998 8.89859 7.79405 9.30705 8.19642C10.0712 8.94956 12.0319 9.72947 12.0361 9.82097Z"
fill="url(#paint5_radial_3681_24667)"
/>
<path
d="M8.00739 7.07982C8.08584 7.13043 8.16368 7.18199 8.24087 7.23451C7.98411 6.33619 7.97319 5.38542 8.20928 4.48145C7.05203 5.00837 6.15193 5.84129 5.49756 6.57676C5.5517 6.57521 7.18571 6.54582 8.00739 7.07982Z"
fill="url(#paint6_radial_3681_24667)"
/>
<path
d="M0.853976 12.2536C1.72318 17.3901 6.37946 21.3118 11.6649 21.4614C16.5568 21.5999 19.6822 18.7605 20.9731 15.9904C21.8075 14.1997 22.3866 11.2701 21.3237 8.38842L21.3233 8.3872L21.3218 8.37628C21.3206 8.36793 21.3199 8.36296 21.3201 8.36568C21.3201 8.36751 21.3208 8.37206 21.3223 8.38443C21.7219 10.9935 20.3947 13.5212 18.3199 15.2302L18.3137 15.2449C14.271 18.5366 10.4024 17.2309 9.61913 16.6964C9.56408 16.67 9.50939 16.6427 9.45507 16.6147C7.0981 15.4884 6.12441 13.3411 6.3332 11.4996C4.34302 11.4996 3.66441 9.82101 3.66441 9.82101C3.66441 9.82101 5.45124 8.54699 7.8062 9.65503C9.98729 10.6813 12.0356 9.8211 12.0359 9.82101C12.0317 9.72951 10.071 8.9496 9.30667 8.19651C8.89824 7.79414 8.70432 7.60012 8.53257 7.45457C8.4394 7.37587 8.34198 7.30232 8.24077 7.23426C8.16349 7.18188 8.08566 7.13031 8.00729 7.07957C7.18566 6.54557 5.5516 6.57496 5.49746 6.57637H5.49226C5.04568 6.01073 5.07718 4.14482 5.10263 3.75524C5.09729 3.7311 4.76949 3.9254 4.72655 3.9547C4.33248 4.23598 3.96408 4.55159 3.62565 4.89782C3.24052 5.28846 2.88865 5.71053 2.57368 6.15965C2.57368 6.16021 2.57335 6.16087 2.57316 6.16143C2.57316 6.16082 2.57349 6.16021 2.57368 6.15965C1.84929 7.18619 1.33553 8.34611 1.0621 9.57238C1.05671 9.59681 0.656632 11.3462 0.853976 12.2536Z"
fill="url(#paint7_radial_3681_24667)"
/>
<path
d="M15.9894 7.80883C16.4004 8.21229 16.7525 8.67169 17.0352 9.17345C17.0972 9.22005 17.1552 9.2665 17.2043 9.31183C19.7582 11.665 18.4201 14.9931 18.3203 15.2303C20.395 13.5212 21.7222 10.9936 21.3227 8.38445C20.0489 5.20951 17.8889 3.92926 16.1251 1.1417C16.0361 1.00075 15.9468 0.85942 15.8598 0.710452C15.8155 0.634372 15.7741 0.556628 15.7357 0.477389C15.6625 0.335913 15.6059 0.186371 15.5673 0.0317953C15.5676 0.0244774 15.5651 0.017323 15.5604 0.0117374C15.5556 0.00615177 15.549 0.00253868 15.5417 0.00160783C15.5348 -0.000373182 15.5275 -0.000373182 15.5206 0.00160783C15.519 0.00217033 15.5166 0.00399845 15.515 0.0046547C15.5125 0.00563908 15.5093 0.00788908 15.5067 0.0093422C15.0712 0.216905 12.4876 4.3075 15.9894 7.80883Z"
fill="url(#paint8_radial_3681_24667)"
/>
<path
d="M17.2043 9.31181C17.1551 9.26648 17.0972 9.22003 17.0352 9.17343C17.0123 9.15618 16.9898 9.13903 16.9652 9.12187C16.3637 8.69934 15.2866 8.28201 14.2489 8.46248C18.3008 10.4881 17.2131 17.4637 11.5984 17.2004C11.0984 17.18 10.6043 17.0847 10.1326 16.9177C10.021 16.8757 9.91066 16.8306 9.80166 16.7824C9.7381 16.7534 9.67458 16.7241 9.61182 16.6917C9.61407 16.6932 9.61716 16.6949 9.61946 16.6964C10.4027 17.2307 14.2713 18.5365 18.314 15.2448L18.3202 15.2302C18.42 14.9932 19.7581 11.665 17.2043 9.31181Z"
fill="url(#paint9_radial_3681_24667)"
/>
<path
d="M6.95794 13.0051C6.95794 13.0051 7.47793 11.0675 10.6813 11.0675C11.0276 11.0675 12.0177 10.1012 12.036 9.82096C12.0543 9.54074 9.98756 10.6812 7.80633 9.65497C5.45138 8.54694 3.66455 9.82096 3.66455 9.82096C3.66455 9.82096 4.34316 11.4995 6.33333 11.4995C6.1246 13.341 7.09828 15.4886 9.45521 16.6147C9.50789 16.6399 9.55739 16.6674 9.61149 16.6915C8.2358 15.9805 7.09983 14.6368 6.95794 13.0051Z"
fill="url(#paint10_radial_3681_24667)"
/>
<path
d="M22.4396 7.79786C21.95 6.62017 20.9583 5.34873 20.1796 4.94687C20.8134 6.1893 21.1802 7.43561 21.3203 8.36575C21.3203 8.36758 21.321 8.37212 21.3225 8.3845C20.0487 5.20951 17.8888 3.92926 16.125 1.1417C16.0359 1.00075 15.9466 0.85942 15.8596 0.710452C15.8153 0.634372 15.774 0.556628 15.7356 0.477389C15.6623 0.335913 15.6058 0.186371 15.5672 0.0317953C15.5675 0.0244774 15.565 0.017323 15.5602 0.0117374C15.5555 0.00615177 15.5489 0.00253868 15.5416 0.00160783C15.5347 -0.000373182 15.5274 -0.000373182 15.5205 0.00160783C15.5189 0.00217033 15.5165 0.00399845 15.5148 0.0046547C15.5123 0.00563908 15.5092 0.00788908 15.5066 0.0093422C15.5078 0.0076547 15.5105 0.00385783 15.5113 0.0029672C12.6814 1.66028 11.7214 4.72614 11.6332 6.26003C11.7645 6.25094 11.8953 6.23992 12.0289 6.23992C14.1408 6.23992 15.9801 7.40101 16.9649 9.12198C16.3634 8.69945 15.2863 8.28212 14.2486 8.46259C18.3005 10.4882 17.2127 17.4638 11.598 17.2005C11.098 17.1801 10.6039 17.0848 10.1322 16.9178C10.0207 16.8758 9.91032 16.8307 9.80133 16.7825C9.73777 16.7535 9.67425 16.7242 9.61148 16.6918C9.61373 16.6933 9.61683 16.6951 9.61912 16.6965C9.56407 16.67 9.50938 16.6427 9.45506 16.6148C9.50775 16.6399 9.55725 16.6675 9.61134 16.6916C8.23556 15.9806 7.09959 14.6369 6.9577 13.0052C6.9577 13.0052 7.47769 11.0676 10.6811 11.0676C11.0274 11.0676 12.0174 10.1013 12.0358 9.82108C12.0316 9.72958 10.0709 8.94967 9.30656 8.19658C8.89814 7.7942 8.70422 7.60019 8.53247 7.45464C8.43929 7.37594 8.34188 7.30239 8.24067 7.23433C7.98391 6.33601 7.97299 5.38524 8.20908 4.48126C7.05183 5.00819 6.15173 5.84111 5.49736 6.57658H5.49216C5.04558 6.01094 5.07708 4.14503 5.10253 3.75545C5.09719 3.73131 4.76939 3.92561 4.72645 3.9549C4.33238 4.23619 3.96398 4.5518 3.62555 4.89803C3.24054 5.28863 2.88878 5.71066 2.57391 6.15972C2.57391 6.16028 2.57358 6.16094 2.57339 6.1615C2.57339 6.16089 2.57372 6.16028 2.57391 6.15972C1.84952 7.18625 1.33576 8.34618 1.06233 9.57245C1.05694 9.59687 1.05239 9.62219 1.04714 9.6468C1.02595 9.74598 0.930609 10.2493 0.917297 10.3572C0.916266 10.3655 0.918281 10.349 0.917297 10.3572C0.830351 10.8773 0.774875 11.4022 0.751172 11.929C0.751172 11.9482 0.75 11.9672 0.75 11.9865C0.75 18.2072 5.79375 23.2501 12.0152 23.2501C17.587 23.2501 22.2133 19.2053 23.119 13.8924C23.1381 13.7482 23.1534 13.6033 23.1702 13.4578C23.3941 11.5261 23.1454 9.49572 22.4396 7.79786ZM21.322 8.37634C21.3226 8.38033 21.3233 8.3845 21.3239 8.38848L21.3235 8.38726L21.322 8.37634Z"
fill="url(#paint11_linear_3681_24667)"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_3681_24667"
x1="20.3814"
y1="3.60386"
x2="2.29295"
y2="21.0528"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.05" stopColor="#FFF44F" />
<stop offset="0.37" stopColor="#FF980E" />
<stop offset="0.53" stopColor="#FF3647" />
<stop offset="0.7" stopColor="#E31587" />
</linearGradient>
<radialGradient
id="paint1_radial_3681_24667"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(16.3404 2.59171) scale(23.0401 23.4281)"
>
<stop offset="0.13" stopColor="#FFBD4F" />
<stop offset="0.28" stopColor="#FF980E" />
<stop offset="0.47" stopColor="#FF3750" />
<stop offset="0.78" stopColor="#EB0878" />
<stop offset="0.86" stopColor="#E50080" />
</radialGradient>
<radialGradient
id="paint2_radial_3681_24667"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(9.65968 12.2681) scale(23.6161 23.4281)"
>
<stop offset="0.3" stopColor="#960E18" />
<stop offset="0.35" stopColor="#B11927" stopOpacity="0.74" />
<stop offset="0.43" stopColor="#DB293D" stopOpacity="0.34" />
<stop offset="0.5" stopColor="#F5334B" stopOpacity="0.09" />
<stop offset="0.53" stopColor="#FF3750" stopOpacity="0" />
</radialGradient>
<radialGradient
id="paint3_radial_3681_24667"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(14.2261 -1.0978) scale(7.56236 12.839)"
>
<stop offset="0.13" stopColor="#FFF44F" />
<stop offset="0.53" stopColor="#FF980E" />
</radialGradient>
<radialGradient
id="paint4_radial_3681_24667"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(9.2356 18.3163) scale(10.007 10.9678)"
>
<stop offset="0.35" stopColor="#3A8EE6" />
<stop offset="0.67" stopColor="#9059FF" />
<stop offset="1" stopColor="#C139E6" />
</radialGradient>
<radialGradient
id="paint5_radial_3681_24667"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(10.9455 9.859) scale(5.31373 6.47101)"
>
<stop offset="0.21" stopColor="#9059FF" stopOpacity="0" />
<stop offset="0.97" stopColor="#6E008B" stopOpacity="0.6" />
</radialGradient>
<radialGradient
id="paint6_radial_3681_24667"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(11.2585 1.72838) scale(7.95561 7.98388)"
>
<stop offset="0.1" stopColor="#FFE226" />
<stop offset="0.79" stopColor="#FF7139" />
</radialGradient>
<radialGradient
id="paint7_radial_3681_24667"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(18.5242 -3.50921) scale(37.9818 31.8836)"
>
<stop offset="0.11" stopColor="#FFF44F" />
<stop offset="0.46" stopColor="#FF980E" />
<stop offset="0.72" stopColor="#FF3647" />
<stop offset="0.9" stopColor="#E31587" />
</radialGradient>
<radialGradient
id="paint8_radial_3681_24667"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(4.41888 7.02007) rotate(77.3946) scale(12.0503 52.1278)"
>
<stop stopColor="#FFF44F" />
<stop offset="0.3" stopColor="#FF980E" />
<stop offset="0.57" stopColor="#FF3647" />
<stop offset="0.74" stopColor="#E31587" />
</radialGradient>
<radialGradient
id="paint9_radial_3681_24667"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(11.3407 4.60001) scale(21.8071 21.424)"
>
<stop offset="0.14" stopColor="#FFF44F" />
<stop offset="0.48" stopColor="#FF980E" />
<stop offset="0.66" stopColor="#FF3647" />
<stop offset="0.9" stopColor="#E31587" />
</radialGradient>
<radialGradient
id="paint10_radial_3681_24667"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(17.0005 5.8529) scale(26.2114 23.4492)"
>
<stop offset="0.09" stopColor="#FFF44F" />
<stop offset="0.63" stopColor="#FF980E" />
</radialGradient>
<linearGradient
id="paint11_linear_3681_24667"
x1="18.75"
y1="3.25511"
x2="4.28552"
y2="19.0592"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.17" stopColor="#FFF44F" stopOpacity="0.8" />
<stop offset="0.6" stopColor="#FFF44F" stopOpacity="0" />
</linearGradient>
<clipPath id="clip0_3681_24667">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>
);
};
export const BrowserIcon = () => {
const { MOZILLA_EXTENSION_URL, CHROME_EXTENSION_URL } = useEnvironment();
const isItChrome = window.navigator.userAgent.includes('Chrome');
const isItMozilla =
window.navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
return (
<div className="absolute right-1 top-0 h-8 flex items-center">
{!isItChrome && !isItMozilla ? (
<>
<a href={MOZILLA_EXTENSION_URL} target="_blank" rel="noreferrer">
<MozillaIcon />
</a>{' '}
<a href={CHROME_EXTENSION_URL} target="_blank" rel="noreferrer">
<ChromeIcon />
</a>
</>
) : (
<>
{isItChrome && <ChromeIcon />}
{isItMozilla && <MozillaIcon />}
</>
)}
</div>
);
};
@@ -1,10 +1,4 @@
import {
act,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import { act, fireEvent, render, screen } from '@testing-library/react';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { VegaWalletProvider } from '../provider';
@@ -18,7 +12,6 @@ import {
ClientErrors,
InjectedConnector,
JsonRpcConnector,
RestConnector,
ViewConnector,
WalletError,
} from '../connectors';
@@ -36,6 +29,12 @@ const mockUpdateDialogOpen = jest.fn();
const mockCloseVegaDialog = jest.fn();
jest.mock('@vegaprotocol/environment');
let mockIsDesktopRunning = true;
jest.mock('../use-is-wallet-service-running', () => ({
useIsWalletServiceRunning: jest
.fn()
.mockImplementation(() => mockIsDesktopRunning),
}));
// @ts-ignore ignore mock implementation
useEnvironment.mockImplementation(() => ({
@@ -53,12 +52,10 @@ let defaultProps: VegaConnectDialogProps;
const INITIAL_KEY = 'some-key';
const rest = new RestConnector();
const jsonRpc = new JsonRpcConnector();
const view = new ViewConnector(INITIAL_KEY);
const injected = new InjectedConnector();
const connectors = {
rest,
jsonRpc,
view,
injected,
@@ -104,6 +101,11 @@ function generateJSX(props?: Partial<VegaConnectDialogProps>) {
}
describe('VegaConnectDialog', () => {
let navigatorGetter: jest.SpyInstance;
beforeEach(() => {
jest.clearAllMocks();
navigatorGetter = jest.spyOn(window.navigator, 'userAgent', 'get');
});
it('displays a list of connection options', async () => {
const { container, rerender } = render(generateJSX());
expect(container).toBeEmptyDOMElement();
@@ -112,133 +114,23 @@ describe('VegaConnectDialog', () => {
expect(list).toBeInTheDocument();
expect(list.children).toHaveLength(3);
expect(screen.getByTestId('connector-jsonRpc')).toHaveTextContent(
'Connect Vega wallet'
);
expect(screen.getByTestId('connector-rest')).toHaveTextContent(
'Hosted Fairground wallet'
);
expect(screen.getByTestId('connector-view')).toHaveTextContent(
'View as vega user'
'Use the Desktop App/CLI'
);
});
it('displays browser wallet option if detected on window object', async () => {
navigatorGetter.mockReturnValue('Chrome');
mockBrowserWallet();
render(generateJSX());
const list = await screen.findByTestId('connectors-list');
expect(list.children).toHaveLength(4);
expect(list.children).toHaveLength(3);
expect(screen.getByTestId('connector-injected')).toHaveTextContent(
'Connect Web wallet'
'Connect'
);
clearBrowserWallet();
});
describe('RestConnector', () => {
it('connects', async () => {
const spy = jest
.spyOn(connectors.rest, 'authenticate')
.mockImplementation(() =>
Promise.resolve({ success: true, error: null })
);
jest
.spyOn(connectors.rest, 'connect')
.mockImplementation(() =>
Promise.resolve([{ publicKey: 'pubkey', name: 'test key 1' }])
);
render(generateJSX());
// Switches to rest form
fireEvent.click(await screen.findByText('Hosted Fairground wallet'));
// Client side validation
fireEvent.submit(screen.getByTestId('rest-connector-form'));
expect(spy).not.toHaveBeenCalled();
await waitFor(() => {
expect(screen.getAllByText('Required')).toHaveLength(2);
});
const fields = fillInForm();
// Wait for auth method to be called
await act(async () => {
fireEvent.submit(screen.getByTestId('rest-connector-form'));
});
await waitFor(() => {
expect(spy).toHaveBeenCalledWith(fields);
expect(mockCloseVegaDialog).toHaveBeenCalled();
});
});
it('handles failed connection', async () => {
const errMessage = 'Error message';
// Error from service
let spy = jest
.spyOn(connectors.rest, 'authenticate')
.mockImplementation(() =>
Promise.resolve({ success: false, error: errMessage })
);
render(generateJSX());
// Switches to rest form
fireEvent.click(await screen.findByText('Hosted Fairground wallet'));
const fields = fillInForm();
fireEvent.submit(screen.getByTestId('rest-connector-form'));
// Wait for auth method to be called
await act(async () => {
fireEvent.submit(screen.getByTestId('rest-connector-form'));
});
expect(spy).toHaveBeenCalledWith(fields);
expect(screen.getByTestId('form-error')).toHaveTextContent(errMessage);
expect(mockUpdateDialogOpen).not.toHaveBeenCalled();
// Fetch failed due to wallet not running
spy = jest
.spyOn(connectors.rest, 'authenticate')
// @ts-ignore test fetch failed with typeerror
.mockImplementation(() =>
Promise.reject(new TypeError('fetch failed'))
);
await act(async () => {
fireEvent.submit(screen.getByTestId('rest-connector-form'));
});
expect(screen.getByTestId('form-error')).toHaveTextContent(
`Wallet not running at ${mockHostedWalletUrl}`
);
// Reject eg non 200 results
spy = jest
.spyOn(connectors.rest, 'authenticate')
// @ts-ignore test fetch failed with typeerror
.mockImplementation(() => Promise.reject(new Error('Error!')));
await act(async () => {
fireEvent.submit(screen.getByTestId('rest-connector-form'));
});
expect(screen.getByTestId('form-error')).toHaveTextContent(
'Authentication failed'
);
});
const fillInForm = () => {
const walletValue = 'test-wallet';
fireEvent.change(screen.getByTestId('rest-wallet'), {
target: { value: walletValue },
});
const passphraseValue = 'test-passphrase';
fireEvent.change(screen.getByTestId('rest-passphrase'), {
target: { value: passphraseValue },
});
return { wallet: walletValue, passphrase: passphraseValue };
};
});
describe('JsonRpcConnector', () => {
const delay = 100;
let spyOnCheckCompat: jest.SpyInstance;
@@ -373,83 +265,36 @@ describe('VegaConnectDialog', () => {
expect(screen.getByText('An unknown error occurred')).toBeInTheDocument();
});
it('handles wallet running is not detected', async () => {
mockIsDesktopRunning = false;
render(generateJSX());
expect(await screen.findByTestId('connector-jsonRpc')).toBeDisabled();
});
it('Mozilla logo should be rendered', async () => {
navigatorGetter.mockReturnValue('Firefox');
render(generateJSX());
expect(await screen.findByTestId('mozilla-logo')).toBeInTheDocument();
});
it('Chrome logo should be rendered', async () => {
navigatorGetter.mockReturnValue('Chrome');
render(generateJSX());
expect(await screen.findByTestId('chrome-logo')).toBeInTheDocument();
});
it('Chrome and Firefox logo should be rendered', async () => {
navigatorGetter.mockReturnValue('Safari');
render(generateJSX());
expect(await screen.findByTestId('mozilla-logo')).toBeInTheDocument();
expect(await screen.findByTestId('chrome-logo')).toBeInTheDocument();
});
async function selectJsonRpc() {
expect(await screen.findByRole('dialog')).toBeInTheDocument();
fireEvent.click(await screen.findByTestId('connector-jsonRpc'));
}
});
describe('ViewOnlyConnector', () => {
const fillInForm = (address = '0'.repeat(64)) => {
fireEvent.change(screen.getByTestId('address'), {
target: { value: address },
});
return { address };
};
it('connects', async () => {
const spy = jest.spyOn(connectors.view, 'connect');
render(generateJSX());
// Switches to view form
fireEvent.click(await screen.findByText('View as vega user'));
// Client side validation
fireEvent.submit(screen.getByTestId('view-connector-form'));
expect(spy).not.toHaveBeenCalled();
await waitFor(() => {
expect(screen.getAllByText('Required')).toHaveLength(1);
});
fillInForm();
// Wait for auth method to be called
await act(async () => {
fireEvent.submit(screen.getByTestId('view-connector-form'));
});
expect(spy).toHaveBeenCalled();
expect(mockCloseVegaDialog).toHaveBeenCalled();
});
it('ensures pubkey is of correct length', async () => {
render(generateJSX());
// Switches to view form
fireEvent.click(await screen.findByText('View as vega user'));
fillInForm('123');
// Wait for auth method to be called
await act(async () => {
fireEvent.submit(screen.getByTestId('view-connector-form'));
});
await waitFor(() => {
expect(
screen.getAllByText('Pubkey must be 64 characters in length')
).toHaveLength(1);
});
});
it('ensures pubkey is of valid hex', async () => {
render(generateJSX());
// Switches to view form
fireEvent.click(await screen.findByText('View as vega user'));
fillInForm('q'.repeat(64));
// Wait for auth method to be called
await act(async () => {
fireEvent.submit(screen.getByTestId('view-connector-form'));
});
await waitFor(() => {
expect(screen.getAllByText('Pubkey must be be valid hex')).toHaveLength(
1
);
});
});
});
describe('InjectedConnector', () => {
beforeAll(() => {
jest.useFakeTimers();
+191 -94
View File
@@ -1,43 +1,51 @@
import classNames from 'classnames';
import { create } from 'zustand';
import {
Button,
Dialog,
FormGroup,
Input,
Intent,
Pill,
TradingButton,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import { useCallback, useState } from 'react';
import type { WalletClientError } from '@vegaprotocol/wallet-client';
import { t } from '@vegaprotocol/i18n';
import type { VegaConnector } from '../connectors';
import { InjectedConnector } from '../connectors';
import { ViewConnector } from '../connectors';
import { JsonRpcConnector, RestConnector } from '../connectors';
import { RestConnectorForm } from './rest-connector-form';
import { JsonRpcConnectorForm } from './json-rpc-connector-form';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import {
InjectedConnector,
JsonRpcConnector,
ViewConnector,
} from '../connectors';
import { JsonRpcConnectorForm } from './json-rpc-connector-form';
import { ViewConnectorForm } from './view-connector-form';
import { useEnvironment } from '@vegaprotocol/environment';
import {
BrowserIcon,
ConnectDialogContent,
ConnectDialogFooter,
ConnectDialogTitle,
} from './connect-dialog-elements';
import type { Status as JsonRpcStatus } from '../use-json-rpc-connect';
import type { Status as InjectedStatus } from '../use-injected-connector';
import { useJsonRpcConnect } from '../use-json-rpc-connect';
import { ViewConnectorForm } from './view-connector-form';
import type { Status as InjectedStatus } from '../use-injected-connector';
import { useInjectedConnector } from '../use-injected-connector';
import { useChainIdQuery } from './__generated__/ChainId';
import { useVegaWallet } from '../use-vega-wallet';
import { useInjectedConnector } from '../use-injected-connector';
import { InjectedConnectorForm } from './injected-connector-form';
import { isBrowserWalletInstalled } from '../utils';
import { useIsWalletServiceRunning } from '../use-is-wallet-service-running';
export const CLOSE_DELAY = 1700;
type Connectors = { [key: string]: VegaConnector };
export type WalletType = 'injected' | 'jsonRpc' | 'rest' | 'view';
export type WalletType = 'injected' | 'jsonRpc' | 'view';
export interface VegaConnectDialogProps {
connectors: Connectors;
riskMessage?: React.ReactNode;
riskMessage?: ReactNode;
}
export interface VegaWalletDialogStore {
@@ -109,9 +117,9 @@ const ConnectDialogContainer = ({
}: {
connectors: Connectors;
appChainId: string;
riskMessage?: React.ReactNode;
riskMessage?: ReactNode;
}) => {
const { VEGA_WALLET_URL, VEGA_ENV, HOSTED_WALLET_URL } = useEnvironment();
const { VEGA_WALLET_URL } = useEnvironment();
const closeDialog = useVegaWalletDialogStore(
(store) => store.closeVegaWalletDialog
);
@@ -135,11 +143,7 @@ const ConnectDialogContainer = ({
const handleSelect = (type: WalletType) => {
const connector = connectors[type];
// If type is rest user has selected the hosted wallet option. So here
// we ensure that we are connecting to https://vega-hosted-wallet.on.fleek.co/
// otherwise use walletUrl which defaults to the localhost:1789
connector.url = type === 'rest' ? HOSTED_WALLET_URL : walletUrl;
connector.url = walletUrl;
if (!connector) {
// we should never get here unless connectors are not configured correctly
@@ -156,6 +160,11 @@ const ConnectDialogContainer = ({
injectedConnect(connector, appChainId);
}
};
const isDesktopWalletRunning = useIsWalletServiceRunning(
walletUrl,
connectors,
appChainId
);
return (
<>
@@ -175,11 +184,11 @@ const ConnectDialogContainer = ({
walletUrl={walletUrl}
setWalletUrl={setWalletUrl}
onSelect={handleSelect}
isMainnet={VEGA_ENV === Networks.MAINNET}
isDesktopWalletRunning={isDesktopWalletRunning}
/>
)}
</ConnectDialogContent>
<ConnectDialogFooter connector={selectedConnector} />
<ConnectDialogFooter />
</>
);
};
@@ -188,52 +197,62 @@ const ConnectorList = ({
onSelect,
walletUrl,
setWalletUrl,
isMainnet,
isDesktopWalletRunning,
}: {
onSelect: (type: WalletType) => void;
walletUrl: string;
setWalletUrl: (value: string) => void;
isMainnet: boolean;
isDesktopWalletRunning: boolean | null;
}) => {
const title = isBrowserWalletInstalled()
? t('Connect Vega wallet')
: t('Get a Vega wallet');
const extendedText = (
<>
<div className="w-full h-full flex justify-center items-center gap-1 text-base">
{t('Connect')}
</div>
<BrowserIcon />
</>
);
return (
<>
<ConnectDialogTitle>{t('Connect')}</ConnectDialogTitle>
<CustomUrlInput walletUrl={walletUrl} setWalletUrl={setWalletUrl} />
<ul data-testid="connectors-list" className="mb-6">
<li className="mb-4 last:mb-0">
<ConnectionOption
type="jsonRpc"
text={t('Connect Vega wallet')}
onClick={() => onSelect('jsonRpc')}
/>
</li>
{'vega' in window && (
<li className="mb-4 last:mb-0">
<ConnectDialogTitle>{title}</ConnectDialogTitle>
<p>
{t(
'Connect securely, deposit funds and approve or reject transactions with the Vega wallet'
)}
</p>
<div data-testid="connectors-list" className="flex flex-col mt-6 gap-2">
<div className="last:mb-0">
{isBrowserWalletInstalled() ? (
<ConnectionOption
type="injected"
text={t('Connect Web wallet')}
text={extendedText}
onClick={() => onSelect('injected')}
/>
</li>
)}
{!isMainnet && (
<li className="mb-4 last:mb-0">
<ConnectionOption
type="rest"
text={t('Hosted Fairground wallet')}
onClick={() => onSelect('rest')}
/>
</li>
)}
<li className="mb-4 last:mb-0">
<div className="my-4 text-center">{t('OR')}</div>
) : (
<GetWallet />
)}
</div>
<div>
<ConnectionOption
type="view"
text={t('View as vega user')}
text={t('View as party')}
onClick={() => onSelect('view')}
/>
</li>
</ul>
</div>
<div className="last:mb-0">
<CustomUrlInput
walletUrl={walletUrl}
setWalletUrl={setWalletUrl}
isDesktopWalletRunning={isDesktopWalletRunning}
onSelect={() => onSelect('jsonRpc')}
/>
</div>
</div>
</>
);
};
@@ -259,7 +278,7 @@ const SelectedForm = ({
};
reset: () => void;
onConnect: () => void;
riskMessage?: React.ReactNode;
riskMessage?: ReactNode;
}) => {
if (connector instanceof InjectedConnector) {
return (
@@ -274,24 +293,6 @@ const SelectedForm = ({
);
}
if (connector instanceof RestConnector) {
return (
<>
<button
onClick={reset}
className="absolute p-2 top-0 left-0 md:top-2 md:left-2"
data-testid="back-button"
>
<VegaIcon name={VegaIconNames.CHEVRON_LEFT} />
</button>
<ConnectDialogTitle>{t('Connect')}</ConnectDialogTitle>
<div className="mb-2">
<RestConnectorForm connector={connector} onConnect={onConnect} />
</div>
</>
);
}
if (connector instanceof JsonRpcConnector) {
return (
<JsonRpcConnectorForm
@@ -305,7 +306,6 @@ const SelectedForm = ({
/>
);
}
if (connector instanceof ViewConnector) {
return (
<ViewConnectorForm
@@ -315,50 +315,116 @@ const SelectedForm = ({
/>
);
}
throw new Error('No connector selected');
};
const GetWallet = () => {
const { MOZILLA_EXTENSION_URL, CHROME_EXTENSION_URL } = useEnvironment();
const isItChrome = window.navigator.userAgent.includes('Chrome');
const isItMozilla =
window.navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
const onClick = () => {
if (isItMozilla) {
window.open(MOZILLA_EXTENSION_URL, '_blank');
return;
}
if (isItChrome) {
window.open(CHROME_EXTENSION_URL, '_blank');
}
};
const buttonContent = (
<>
<div className="flex items-center justify-center gap-1 text-base">
{t('Get the Vega Wallet')}
<Pill size="xxs" intent={Intent.Info}>
ALPHA
</Pill>
</div>
<BrowserIcon />
</>
);
return !isItChrome && !isItMozilla ? (
<div
className={classNames([
'bg-vega-blue-350 hover:bg-vega-blue-400 dark:bg-vega-blue-650 dark:hover:bg-vega-blue-600',
'flex gap-2 items-center justify-center rounded h-8 px-3 relative',
])}
data-testid="get-wallet-button"
>
{buttonContent}
</div>
) : (
<TradingButton
onClick={onClick}
intent={Intent.Info}
data-testid="get-wallet-button"
className="relative"
size="small"
fill
>
{buttonContent}
</TradingButton>
);
};
const ConnectionOption = ({
disabled,
type,
text,
onClick,
icon,
}: {
type: WalletType;
text: string;
text: string | ReactNode;
onClick: () => void;
disabled?: boolean;
icon?: ReactNode;
}) => {
return (
<Button
<TradingButton
size="small"
intent={Intent.Info}
onClick={onClick}
size="lg"
fill={true}
variant={['rest', 'view'].includes(type) ? 'default' : 'primary'}
className="relative"
data-testid={`connector-${type}`}
disabled={disabled}
icon={icon}
fill
>
<span className="-mx-10 flex text-left justify-between items-center">
{text}
<VegaIcon name={VegaIconNames.ARROW_RIGHT} />
</span>
</Button>
<span className="flex justify-center items-center text-base">{text}</span>
</TradingButton>
);
};
const CustomUrlInput = ({
walletUrl,
setWalletUrl,
isDesktopWalletRunning,
onSelect,
}: {
walletUrl: string;
setWalletUrl: (url: string) => void;
isDesktopWalletRunning: boolean | null;
onSelect: (type: WalletType) => void;
}) => {
const [urlInputExpanded, setUrlInputExpanded] = useState(false);
return urlInputExpanded ? (
<>
<p className="mb-2">{t('Custom wallet location')}</p>
<div className="flex justify-between mb-1.5">
<p className="text-sm text-secondary">{t('Custom wallet location')}</p>
<button
className="text-sm underline"
onClick={() => setUrlInputExpanded(false)}
>
<VegaIcon name={VegaIconNames.ARROW_LEFT} /> {t('Go back')}
</button>
</div>
<FormGroup
labelFor="wallet-url"
label={t('Custom wallet location')}
hideLabel={true}
hideLabel
>
<Input
value={walletUrl}
@@ -366,17 +432,48 @@ const CustomUrlInput = ({
name="wallet-url"
/>
</FormGroup>
<p className="mb-2">{t('Choose wallet app to connect')}</p>
<ConnectionOption
disabled={!isDesktopWalletRunning}
type="jsonRpc"
text={t('Connect the App/CLI')}
onClick={() => onSelect('jsonRpc')}
/>
</>
) : (
<p className="mb-6">
{t(
'Choose wallet app to connect, or to change port or server URL enter a '
<>
<ConnectionOption
disabled={!isDesktopWalletRunning}
type="jsonRpc"
text={t('Use the Desktop App/CLI')}
onClick={() => onSelect('jsonRpc')}
/>
{isDesktopWalletRunning !== null && (
<p className="mb-6 text-sm pt-2">
{isDesktopWalletRunning ? (
<button
className="underline text-default"
onClick={() => setUrlInputExpanded(true)}
>
{t('Enter a custom wallet location')}{' '}
<VegaIcon name={VegaIconNames.ARROW_RIGHT} />
</button>
) : (
<>
<span className="text-default">
{t(
'No running Desktop App/CLI detected. Open your app now to connect or enter a'
)}
</span>{' '}
<button
className="underline"
onClick={() => setUrlInputExpanded(true)}
>
{t('custom wallet location')}
</button>
</>
)}
</p>
)}
<button className="underline" onClick={() => setUrlInputExpanded(true)}>
{t('custom wallet location')}
</button>{' '}
{t(' first')}
</p>
</>
);
};
@@ -1,90 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { Button, FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import type { RestConnector } from '../connectors';
import { useVegaWallet } from '../use-vega-wallet';
interface FormFields {
wallet: string;
passphrase: string;
}
interface RestConnectorFormProps {
connector: RestConnector;
onConnect: (connector: RestConnector) => void;
}
export function RestConnectorForm({
connector,
onConnect,
}: RestConnectorFormProps) {
const { connect } = useVegaWallet();
const [error, setError] = useState('');
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormFields>();
async function onSubmit(fields: FormFields) {
const authFailedMessage = t('Authentication failed');
try {
setError('');
const res = await connector.authenticate({
wallet: fields.wallet,
passphrase: fields.passphrase,
});
if (res.success) {
await connect(connector);
onConnect(connector);
} else {
setError(res.error || authFailedMessage);
}
} catch (err) {
if (err instanceof TypeError) {
setError(t(`Wallet not running at ${connector.url}`));
} else if (err instanceof Error) {
setError(authFailedMessage);
} else {
setError(t('Something went wrong'));
}
}
}
return (
<form onSubmit={handleSubmit(onSubmit)} data-testid="rest-connector-form">
<FormGroup label={t('Wallet name')} labelFor="wallet">
<Input
{...register('wallet', { required: t('Required') })}
id="wallet"
data-testid="rest-wallet"
type="text"
/>
{errors.wallet?.message && (
<InputError intent="danger">{errors.wallet.message}</InputError>
)}
</FormGroup>
<FormGroup label={t('Passphrase')} labelFor="passphrase">
<Input
{...register('passphrase', { required: t('Required') })}
id="passphrase"
data-testid="rest-passphrase"
type="password"
/>
{errors.passphrase?.message && (
<InputError intent="danger">{errors.passphrase.message}</InputError>
)}
{error && (
<InputError intent="danger" data-testid="form-error">
{error}
</InputError>
)}
</FormGroup>
<Button variant="primary" type="submit" fill={true}>
{t('Connect')}
</Button>
</form>
);
}
@@ -1,21 +1,23 @@
import { t } from '@vegaprotocol/i18n';
import {
Button,
FormGroup,
Input,
InputError,
Intent,
TradingButton,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useForm } from 'react-hook-form';
import type { ViewConnector } from '../connectors';
import { useVegaWallet } from '../use-vega-wallet';
import { ConnectDialogTitle } from './connect-dialog-elements';
interface FormFields {
address: string;
}
interface RestConnectorFormProps {
interface ViewConnectorFormProps {
connector: ViewConnector;
onConnect: (connector: ViewConnector) => void;
reset?: () => void;
@@ -25,7 +27,7 @@ export function ViewConnectorForm({
connector,
onConnect,
reset,
}: RestConnectorFormProps) {
}: ViewConnectorFormProps) {
const { connect } = useVegaWallet();
const {
register,
@@ -51,23 +53,8 @@ export function ViewConnectorForm({
return (
<>
{reset && (
<button
onClick={reset}
className="absolute p-2 top-0 left-0 md:top-2 md:left-2"
data-testid="back-button"
>
<VegaIcon
name={VegaIconNames.CHEVRON_LEFT}
aria-label="back"
size={16}
/>
</button>
)}
<ConnectDialogTitle>{t('VIEW AS VEGA USER')}</ConnectDialogTitle>
<form onSubmit={handleSubmit(onSubmit)} data-testid="view-connector-form">
<h1 className="text-2xl uppercase mb-6 text-center font-alpha calt">
{t('VIEW AS VEGA USER')}
</h1>
<p className="mb-4">
{t(
'Browse from the perspective of another Vega user in read-only mode.'
@@ -87,14 +74,25 @@ export function ViewConnectorForm({
<InputError intent="danger">{errors.address.message}</InputError>
)}
</FormGroup>
<Button
<TradingButton
data-testid="connect"
variant="primary"
intent={Intent.Info}
type="submit"
fill={true}
fill
>
{t('Browse network')}
</Button>
</TradingButton>
{reset && (
<div className="flex justify-end">
<button
onClick={reset}
className="p-2 text-sm underline"
data-testid="back-button"
>
<VegaIcon name={VegaIconNames.ARROW_LEFT} /> {t('Go back')}
</button>
</div>
)}
</form>
</>
);
-1
View File
@@ -1,5 +1,4 @@
export * from './vega-connector';
export * from './rest-connector';
export * from './injected-connector';
export * from './json-rpc-connector';
export * from './view-connector';
@@ -40,6 +40,7 @@ export class JsonRpcConnector implements VegaConnector {
address: cfg.url,
token: cfg.token ?? undefined,
onTokenChange: (token) => {
this.token = token;
setConfig({
token,
connector: 'jsonRpc',
@@ -55,12 +56,14 @@ export class JsonRpcConnector implements VegaConnector {
this.client = new WalletClient({
address: url,
token: this.token ?? undefined,
onTokenChange: (token) =>
onTokenChange: (token) => {
this.token = token;
setConfig({
token,
url,
connector: 'jsonRpc',
}),
});
},
});
}
get url() {
@@ -1,284 +0,0 @@
import * as Sentry from '@sentry/react';
import { clearConfig, getConfig, setConfig } from '../storage';
import type { Transaction, VegaConnector } from './vega-connector';
import { WalletError } from './vega-connector';
import { z } from 'zod';
import { t } from '@vegaprotocol/i18n';
type TransactionError =
| {
errors: {
[key: string]: string[];
};
details?: string[];
}
| {
error: string;
details?: string[];
};
const VERSION = 'v1';
// Perhaps there should be a default ConnectorConfig that others can extend off. Do all connectors
// need to use local storage, I don't think so...
enum Endpoints {
Auth = 'auth/token',
Command = 'command/sync',
Keys = 'keys',
}
export const AuthTokenSchema = z.object({
token: z.string(),
});
export const TransactionResponseSchema = z.object({
txHash: z.string(),
tx: z.object({
signature: z.object({
value: z.string(),
}),
}),
sentAt: z.string(),
receivedAt: z.string(),
});
export type V1TransactionResponse = z.infer<typeof TransactionResponseSchema>;
export const GetKeysSchema = z.object({
keys: z.array(
z.object({
algorithm: z.object({
name: z.string(),
version: z.number(),
}),
index: z.number(),
meta: z.array(
z.object({
key: z.string(),
value: z.string(),
})
),
pub: z.string(),
tainted: z.boolean(),
})
),
});
/**
* Connector for using the Vega Wallet Service rest api, requires authentication to get a session token
*/
export class RestConnector implements VegaConnector {
url: string | null = null;
token: string | null = null;
constructor() {
const cfg = getConfig();
if (cfg) {
this.token = cfg.token;
this.url = cfg.url;
}
}
async sessionActive() {
return Boolean(this.token);
}
async authenticate(params: { wallet: string; passphrase: string }) {
try {
const res = await this.request(Endpoints.Auth, {
method: 'post',
body: JSON.stringify(params),
});
if (res.status === 403) {
return { success: false, error: t('Invalid credentials') };
}
if (res.error) {
return { success: false, error: res.error };
}
const data = AuthTokenSchema.parse(res.data);
// Store the token, and other things for later
setConfig({
connector: 'rest',
token: data.token,
url: this.url,
});
this.token = data.token;
return { success: true, error: null };
} catch (err) {
return { success: false, error: 'Authentication failed' };
}
}
async connect() {
try {
const res = await this.request(Endpoints.Keys, {
method: 'get',
headers: {
authorization: `Bearer ${this.token}`,
},
});
if (res.error) {
return null;
}
const data = GetKeysSchema.parse(res.data);
return data.keys.map((k) => {
const nameMeta = k.meta.find((m) => m.key === 'name');
return {
publicKey: k.pub,
name: nameMeta ? nameMeta.value : t('No name'),
};
});
} catch (err) {
// keysGet failed, its likely that the session has expired so remove the token from storage
clearConfig();
return null;
}
}
async disconnect() {
try {
await this.request(Endpoints.Auth, {
method: 'delete',
headers: {
authorization: `Bearer ${this.token}`,
},
});
} catch (err) {
Sentry.captureException(err);
} finally {
// Always clear config, if authTokenDelete fails the user still tried to
// connect so clear the config (and containing token) from storage
clearConfig();
}
}
async sendTx(pubKey: string, transaction: Transaction) {
const body = {
pubKey,
propagate: true,
...transaction,
};
const res = await this.request(Endpoints.Command, {
method: 'post',
body: JSON.stringify(body),
headers: {
authorization: `Bearer ${this.token}`,
},
});
// User rejected
if (res.status === 401) {
return null;
}
if (res.error) {
throw new WalletError(res.error, 1, res.details);
}
const data = TransactionResponseSchema.parse(res.data);
// Make return value match that of v2 service
return {
transactionHash: data.txHash,
signature: data.tx.signature.value,
receivedAt: data.receivedAt,
sentAt: data.sentAt,
};
}
/** Parse more complex error object into a single string */
private parseError(err: TransactionError): string {
if ('error' in err) {
return err.error;
}
if ('errors' in err) {
const result = Object.entries(err.errors)
.map((entry) => {
return `${entry[0]}: ${entry[1].join(' | ')}`;
})
.join(', ');
return result;
}
return t('Something went wrong');
}
/** Parse error details array into a single string */
private parseErrorDetails(err: TransactionError): string | null {
if (err.details && err.details.length > 0) {
return err.details.join(', ');
}
return null;
}
private async request(
endpoint: Endpoints,
options: RequestInit
): Promise<{
status?: number;
data?: unknown;
error?: string;
details?: string;
}> {
try {
const fetchResult = await fetch(
`${this.url}/api/${VERSION}/${endpoint}`,
{
...options,
headers: {
...options.headers,
'Content-Type': 'application/json',
},
}
);
if (!fetchResult.ok) {
const errorData = await fetchResult.json();
const error = this.parseError(errorData);
const errorDetails = this.parseErrorDetails(errorData);
if (errorDetails) {
return {
status: fetchResult.status,
error,
details: errorDetails,
};
}
return {
status: fetchResult.status,
error,
};
}
// auth/token delete doesnt return json
if (endpoint === 'auth/token' && options.method === 'delete') {
const textResult = await fetchResult.text();
return {
status: fetchResult.status,
data: textResult,
};
} else {
const jsonResult = await fetchResult.json();
return {
status: fetchResult.status,
data: jsonResult,
};
}
} catch (err) {
return {
error: 'No wallet detected',
};
}
}
}
@@ -154,6 +154,7 @@ interface ProposalNewMarketTerms {
auctionExtension: string;
};
logNormal: LogNormal;
successor?: Successor;
};
};
closingTimestamp: number;
@@ -298,6 +299,11 @@ interface Condition {
value: string;
}
interface Successor {
parentMarketId: string;
insurancePoolFraction: string;
}
interface LogNormal {
tau: number;
riskAversionParameter: number;
-1
View File
@@ -13,6 +13,5 @@ export * from './provider';
export * from './connect-dialog';
export * from './utils';
export * from './storage';
export * from './is-browser-wallet-installed';
export * from './__generated__/TransactionResult';
export * from './__generated__/WithdrawalApproval';
@@ -1 +0,0 @@
export const isBrowserWalletInstalled = () => Boolean(window.vega);
+16 -10
View File
@@ -1,7 +1,6 @@
import { act, renderHook } from '@testing-library/react';
import type { Transaction } from './connectors';
import { ViewConnector } from './connectors';
import { RestConnector } from './connectors';
import { ViewConnector, JsonRpcConnector } from './connectors';
import { useVegaWallet } from './use-vega-wallet';
import { VegaWalletProvider } from './provider';
import { LocalStorage } from '@vegaprotocol/utils';
@@ -10,7 +9,7 @@ import { WALLET_KEY } from './storage';
import * as Environment from '@vegaprotocol/environment';
import * as ReactHelpers from '@vegaprotocol/react-helpers';
const restConnector = new RestConnector();
const jsonRpcConnector = new JsonRpcConnector();
const viewConnector = new ViewConnector();
const setup = () => {
@@ -30,14 +29,21 @@ describe('VegaWalletProvider', () => {
{ publicKey: '222', name: 'public key 2' },
];
const spyOnConnect = jest
.spyOn(restConnector, 'connect')
.spyOn(jsonRpcConnector, 'connect')
.mockImplementation(() => Promise.resolve(mockPubKeys));
const spyOnSend = jest
.spyOn(restConnector, 'sendTx')
.mockImplementation(() => Promise.resolve(null));
.spyOn(jsonRpcConnector, 'sendTx')
.mockImplementation(() =>
Promise.resolve({
transactionHash: 'tsx',
sentAt: '',
receivedAt: '',
signature: '',
})
);
const storageSpy = jest.spyOn(LocalStorage, 'setItem');
const spyOnDisconnect = jest
.spyOn(restConnector, 'disconnect')
.spyOn(jsonRpcConnector, 'disconnect')
.mockImplementation(() => Promise.resolve());
it('connects, disconnects and retrieve keypairs', async () => {
@@ -58,7 +64,7 @@ describe('VegaWalletProvider', () => {
// Connect
await act(async () => {
result.current.connect(restConnector);
result.current.connect(jsonRpcConnector);
});
expect(spyOnConnect).toHaveBeenCalled();
expect(result.current.pubKeys).toHaveLength(mockPubKeys.length);
@@ -99,7 +105,7 @@ describe('VegaWalletProvider', () => {
// Connect
await act(async () => {
result.current.connect(restConnector);
result.current.connect(jsonRpcConnector);
result.current.selectPubKey(mockPubKeys[0].publicKey);
});
expect(spyOnConnect).toHaveBeenCalled();
@@ -119,7 +125,7 @@ describe('VegaWalletProvider', () => {
expect(result.current.pubKey).toBe(null);
await act(async () => {
result.current.connect(restConnector);
result.current.connect(jsonRpcConnector);
result.current.selectPubKey(mockPubKeys[0].publicKey);
});
expect(result.current.pubKey).toBe(mockPubKeys[0].publicKey);
+1 -1
View File
@@ -2,7 +2,7 @@ import { LocalStorage } from '@vegaprotocol/utils';
interface ConnectorConfig {
token: string | null;
connector: 'injected' | 'rest' | 'jsonRpc' | 'view';
connector: 'injected' | 'jsonRpc' | 'view';
url: string | null;
}
@@ -0,0 +1,42 @@
import type { VegaConnector } from './connectors';
import { useCallback, useEffect, useState } from 'react';
import type { JsonRpcConnector } from './connectors';
import { ClientErrors } from './connectors';
export const useIsWalletServiceRunning = (
url: string,
connectors: { [key: string]: VegaConnector },
appChainId: string
) => {
const [run, setRun] = useState<boolean | null>(null);
const checkState = useCallback(async () => {
const connector = connectors['jsonRpc'] as JsonRpcConnector;
connector.url = url;
try {
await connector.checkCompat();
const chainIdResult = await connector.getChainId();
if (chainIdResult.chainID !== appChainId) {
throw ClientErrors.WRONG_NETWORK;
}
} catch (e) {
return false;
}
return true;
}, [connectors, url, appChainId]);
useEffect(() => {
let interval: NodeJS.Timeout;
checkState().then((value) => {
setRun(value);
interval = setInterval(async () => {
setRun(await checkState());
}, 1000 * 10);
});
return () => {
clearInterval(interval);
};
}, [checkState]);
return run;
};
+2
View File
@@ -63,3 +63,5 @@ export const normalizeTransfer = <T extends Exact<Transfer, T>>(
oneOff: {},
};
};
export const isBrowserWalletInstalled = () => Boolean(window.vega);
+16 -11
View File
@@ -6,9 +6,24 @@ When looking to use Vega via a user interface e.g. Dapp (Decentralized web App),
- If the app loads and already has a connection it can restore "eagerly" (without the user having to click connect) it **could** do so
- **must** select a connection method / wallet type: (<a name="0002-WCON-002" href="#0002-WCON-002">0002-WCON-002</a>)
- if Rest:
- If I don't have the browser wallet installed, I see "get started" on the connect button, otherwise I see "Connect" there. (<a name="0002-WCON-002" href="#0002-WCON-002">0002-WCON-002</a>)
- If I don't have the browser wallet installed, when I press "Get started" I can see immediately a way to get the Vega wallet browser extension. (<a name="0002-WCON-0010" href="#0002-WCON-0010">0002-WCON-0010</a>)
- If I do have the browser wallet installed, I can easily choose to connect to it. (<a name="0002-WCON-0011" href="#0002-WCON-0011">0002-WCON-0011</a>)
- If the desktop wallet or CLI is detected as running, I can see that and choose to connect with my desktop / CLI wallet. (<a name="0002-WCON-0012" href="#0002-WCON-0012">0002-WCON-0012</a>)
- If there is not running desktop wallet or CLI detected, I can see that I need to open my wallet. (<a name="0002-WCON-0013" href="#0002-WCON-0013">0002-WCON-0013</a>)
- I can find out more about supported browsers i.e. there is a link (Issue "List compatible browsers" vegawallet-browser#360 has to be implemented). (<a name="0002-WCON-0013" href="#0002-WCON-0013">0002-WCON-0013</a>)
- I can find out more about the Vega Wallet and see what "other" versions there are i.e. there is a link to the page on the website (currently - https://vega.xyz/wallet#overview). (<a name="0002-WCON-0014" href="#0002-WCON-0014">0002-WCON-0014</a>)
- Browser wallet:
- The browser extension you need is automatically detected if you are using Chrome or Firefox, presenting the specific call to action to install that browser extension in a visible way e.g. with a Chrome or Firefox icon. (<a name="0002-WCON-041" href="#0002-WCON-041">0002-WCON-041</a>)
- The browser extension store opens in a new tab on the Vega Wallet extension page (Chrome or Firefox). (<a name="0002-WCON-042" href="#0002-WCON-042">0002-WCON-042</a>)
- When the browser I am using is not Firefox or Chrome, there is a way to download the browser extension anyway but at my own risk i.e. I can see options for both the chrome and firefox extensions in the CTA. (<a name="0002-WCON-043" href="#0002-WCON-043">0002-WCON-043</a>)
- There is a way to understand the browser extension is an Alpha release e.g. there is a label / description. (<a name="0002-WCON-044" href="#0002-WCON-044">0002-WCON-044</a>)
- if I choose Desktop/CLI App ("jsonRpc" type):
- **must** have the option to input a non-default Wallet location (<a name="0002-WCON-003" href="#0002-WCON-003">0002-WCON-003</a>)
- If I select to enter a custom wallet location, there is a way to go back to the default view i.e. a back button or similar.
- **must** submit attempt to connect to wallet (<a name="0002-WCON-005" href="#0002-WCON-005">0002-WCON-005</a>)
- if the dapp DOES already have a permission with the wallet: **must** see that wallet is connected (<a name="0002-WCON-007" href="#0002-WCON-007">0002-WCON-007</a>) note: if the user want to connect to a different wallet to the one that they were previously connected with, they will have to hit logout.
@@ -25,16 +40,6 @@ When looking to use Vega via a user interface e.g. Dapp (Decentralized web App),
- if the dapp is unable to connect for technical reason (e.g. CORS): **must** see an explanation of the error, and a method of fixing the issue (<a name="0002-WCON-016" href="#0002-WCON-016">0002-WCON-016</a>)
- ~~Browser wallet~~ `not available yet`
- Fairground hosted wallet
- **must** only be be shown this option if the dapp is connected to fairground (<a name="0002-WCON-039" href="#0002-WCON-039">0002-WCON-039</a>)
- **must** input a wallet name (<a name="0002-WCON-017" href="#0002-WCON-017">0002-WCON-017</a>)
- **must** input a password (<a name="0002-WCON-018" href="#0002-WCON-018">0002-WCON-018</a>)
- if success: **must** see that the wallet is connected and details of connected key (<a name="0002-WCON-019" href="#0002-WCON-019">0002-WCON-019</a>)
- if failure: **must** see reason for failure (<a name="0002-WCON-020" href="#0002-WCON-020">0002-WCON-020</a>)
- _note: the fairground hosted wallet is configured to automatically approve connections from dapps so there is no need for key selection._
- **must** have the option to select a different method / wallet type if I change my mind (<a name="0002-WCON-021" href="#0002-WCON-021">0002-WCON-021</a>)
... so I can use the interface to read data about my key/party or request my wallet to broadcast transactions to a Vega network.
## Disconnect wallet
+4
View File
@@ -32,4 +32,8 @@
- **must** I can see an explanation of what "Parent market ID" using a tooltip (<a name="3003-PMAN-011" href="#3003-PMAN-011">3003-PMAN-011</a>)
If the proposal has been enacted, I can see the status of the market i.e. opening auction, continuous trading, rejected
- **must** be able to see the market specification with the proposed update (<a name="3003-PMAN-012" href="#3003-PMAN-012">3003-PMAN-012</a>)
- **must** be a simple way to visually highlight / compare the difference with this market specification from the original markets specification (<a name="3003-PMAN-013" href="#3003-PMAN-013">3003-PMAN-013</a>)
back to [Propose](./3002-PROP-propose.md) for details on proposing
+1
View File
@@ -44,6 +44,7 @@
- by newest markets (opening timestamp) (<a name="6001-MARK-032" href="#6001-MARK-032">6001-MARK-032</a>)
- **Must** be able to close and open the market selector (<a name="6001-MARK-066" href="#6001-MARK-066">6001-MARK-066</a>)
- **Must** must change color and have + or negative suffix of the price change and change color for the sparkline (<a name="6001-MARK-067" href="#6001-MARK-067">6001-MARK-067</a>)
- **Must** be default tab "All" where there's no filtering by product. (<a name="6001-MARK-070" href="#6001-MARK-070">6001-MARK-070</a>)
## All Markets
+1 -1
View File
@@ -69,7 +69,7 @@ When looking at a list of orders, I...
- **must** see if any execution flags (aka conditions. e.g. Post only, Reduce only) were applied to the order (<a name="7003-MORD-019" href="#7003-MORD-019">7003-MORD-019</a>)
- **should** see "created at" [time](9001-DATA-data_display.md#time)
- **could** see updated at (this is used by the system when an order is amended, or repriced (in pegged and LP) not sure this in needed)
- **must** be able to see a single order view dialog with all the fields present above
- **should** see time priority (how many orders are before mine at this price)
- if the order is `Active` &amp; **not** part of a liquidity or peg shape: **must** see an option to [amend](#amend-order---price) the individual order (<a name="7003-MORD-007" href="#7003-MORD-007">7003-MORD-007</a>)
- if the order is `Active` &amp; is part of a liquidity or peg shape: **must** **not** see an option to amend the individual order (<a name="7003-MORD-008" href="#7003-MORD-008">7003-MORD-008</a>)