Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeb24a0cee | ||
|
|
e448380959 | ||
|
|
c8c52753df | ||
|
|
b7e1926565 | ||
|
|
6ef3a8b29c | ||
|
|
7af55cc160 | ||
|
|
0f4f7e88c3 | ||
|
|
627e7b9afb | ||
|
|
48ee9205ac | ||
|
|
33f7f8aa82 | ||
|
|
1258e1225b | ||
|
|
f74c3b7159 | ||
|
|
b4d0d09e98 | ||
|
|
c516ca001e | ||
|
|
f65d560f54 | ||
|
|
f1a3120638 | ||
|
|
27e1658f63 | ||
|
|
a9db579f0b | ||
|
|
5d0d31679e | ||
|
|
20c9a91a4d | ||
|
|
5af9fdde7c | ||
|
|
9cbd484317 | ||
|
|
a10e3a43a9 | ||
|
|
5c18c898b0 | ||
|
|
4fe81cc4aa | ||
|
|
0fb8ee3abb | ||
|
|
3422b99491 | ||
|
|
287e294281 | ||
|
|
dc959025c6 | ||
|
|
63bfcc8f65 | ||
|
|
952e906eac | ||
|
|
e765c247ef | ||
|
|
52dea6d0dc | ||
|
|
97f243e5f7 | ||
|
|
9992d9f053 | ||
|
|
3e26431e8f | ||
|
|
6a9f15f59e | ||
|
|
4684745382 | ||
|
|
927e21b045 |
@@ -125,7 +125,7 @@ jobs:
|
||||
#----------------------------------------------
|
||||
- name: Run tests
|
||||
working-directory: ./console-test
|
||||
run: poetry run pytest -s --numprocesses auto --dist loadfile
|
||||
run: poetry run pytest -v -s --numprocesses auto --dist loadfile --durations=20
|
||||
- name: Check files
|
||||
run: |
|
||||
ls -al .
|
||||
|
||||
@@ -15,6 +15,7 @@ on:
|
||||
- types
|
||||
- utils
|
||||
- i18n
|
||||
- wallet
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"proposalSubmission": {
|
||||
"rationale": {
|
||||
"title": "Test new asset proposal",
|
||||
"description": "E2E test for proposals"
|
||||
},
|
||||
"terms": {
|
||||
"newAsset": {
|
||||
"changes": {
|
||||
"name": "USDT Coin",
|
||||
"symbol": "USDT",
|
||||
"decimals": "18",
|
||||
"quantum": "1",
|
||||
"erc20": {
|
||||
"contractAddress": "0xb404c51bbc10dcbe948077f18a4b8e553d160084",
|
||||
"withdrawThreshold": "10",
|
||||
"lifetimeLimit": "10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"closingTimestamp": 1724339572,
|
||||
"enactmentTimestamp": 1724339572,
|
||||
"validationTimestamp": 1692799617
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { getNewAssetTxBody } from '../support/governance.functions';
|
||||
|
||||
context('Proposal page', { tags: '@smoke' }, function () {
|
||||
describe('Verify elements on page', function () {
|
||||
const proposalHeading = 'proposals-heading';
|
||||
const dateTimeRegex =
|
||||
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
|
||||
const proposalTitle = 'Add Lorem Ipsum market';
|
||||
|
||||
before('Create market proposal', function () {
|
||||
cy.visit('/');
|
||||
@@ -11,6 +12,8 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
|
||||
it('Able to view proposal', function () {
|
||||
const proposalTitle = 'Add Lorem Ipsum market';
|
||||
|
||||
cy.navigate_to('governanceProposals');
|
||||
cy.getByTestId(proposalHeading).should('be.visible');
|
||||
cy.contains(proposalTitle)
|
||||
@@ -22,6 +25,9 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
cy.get_element_by_col_id('type').should('have.text', 'NewMarket');
|
||||
cy.get_element_by_col_id('state').should('have.text', 'Enacted');
|
||||
cy.getByTestId('vote-progress').should('be.visible');
|
||||
cy.getByTestId('vote-progress-bar-for')
|
||||
.invoke('attr', 'style')
|
||||
.should('eq', 'width: 100%;');
|
||||
cy.get('[col-id="cDate"]')
|
||||
.invoke('text')
|
||||
.should('match', dateTimeRegex);
|
||||
@@ -35,9 +41,12 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
cy.getByTestId('dialog-title').should('have.text', proposalTitle);
|
||||
cy.get('.language-json').should('exist');
|
||||
cy.getByTestId('icon-cross').click();
|
||||
});
|
||||
|
||||
it.skip('Proposal page displayed on mobile', function () {
|
||||
const proposalTitle = 'Add Lorem Ipsum market';
|
||||
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.navigate_to('governanceProposals', true);
|
||||
cy.getByTestId(proposalHeading).should('be.visible');
|
||||
@@ -45,5 +54,40 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to view new asset proposal', function () {
|
||||
const proposalTitle = 'Test new asset proposal';
|
||||
const newAssetProposalBody = getNewAssetTxBody();
|
||||
cy.VegaWalletSubmitProposal(newAssetProposalBody);
|
||||
|
||||
cy.visit('/');
|
||||
cy.navigate_to('governanceProposals');
|
||||
cy.contains(proposalTitle)
|
||||
.parent()
|
||||
.parent()
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
|
||||
cy.get_element_by_col_id('type').should('have.text', 'NewAsset');
|
||||
cy.get_element_by_col_id('state').should(
|
||||
'have.text',
|
||||
'Waiting for Node Vote'
|
||||
);
|
||||
cy.getByTestId('vote-progress').should('be.visible');
|
||||
cy.getByTestId('vote-progress-bar-against')
|
||||
.invoke('attr', 'style')
|
||||
.should('eq', 'width: 100%;');
|
||||
cy.get('[col-id="cDate"]')
|
||||
.invoke('text')
|
||||
.should('match', dateTimeRegex);
|
||||
cy.get('[col-id="eDate"]')
|
||||
.invoke('text')
|
||||
.should('match', dateTimeRegex);
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
.and('contains', 'https://governance.fairground.wtf/proposals/');
|
||||
cy.contains('View terms').should('exist').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import { addSeconds, millisecondsToSeconds } from 'date-fns';
|
||||
|
||||
export function createSuccessorMarketProposal(parentMarketId) {
|
||||
cy.VegaWalletSubmitProposal(getSuccessorTxBody(parentMarketId));
|
||||
}
|
||||
|
||||
function getSuccessorTxBody(parentMarketId) {
|
||||
const MIN_CLOSE_SEC = 500;
|
||||
const MIN_ENACT_SEC = 700;
|
||||
|
||||
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: {
|
||||
@@ -122,8 +132,49 @@ function getSuccessorTxBody(parentMarketId) {
|
||||
},
|
||||
},
|
||||
},
|
||||
closingTimestamp: 1695666618,
|
||||
enactmentTimestamp: 1695666618,
|
||||
closingTimestamp,
|
||||
enactmentTimestamp,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getNewAssetTxBody() {
|
||||
const MIN_CLOSE_SEC = 500;
|
||||
const MIN_ENACT_SEC = 700;
|
||||
const MIN_VALID_SEC = 60;
|
||||
|
||||
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
|
||||
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
|
||||
const validationDate = addSeconds(new Date(), MIN_VALID_SEC);
|
||||
|
||||
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
|
||||
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
|
||||
const validationTimestamp = millisecondsToSeconds(validationDate.getTime());
|
||||
|
||||
return {
|
||||
proposalSubmission: {
|
||||
rationale: {
|
||||
title: 'Test new asset proposal',
|
||||
description: 'E2E test for proposals',
|
||||
},
|
||||
terms: {
|
||||
newAsset: {
|
||||
changes: {
|
||||
name: 'USDT Coin',
|
||||
symbol: 'USDT',
|
||||
decimals: '18',
|
||||
quantum: '1',
|
||||
erc20: {
|
||||
contractAddress: '0xb404c51bbc10dcbe948077f18a4b8e553d160084',
|
||||
withdrawThreshold: '10',
|
||||
lifetimeLimit: '10',
|
||||
},
|
||||
},
|
||||
},
|
||||
closingTimestamp,
|
||||
enactmentTimestamp,
|
||||
validationTimestamp,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ export const TxDetailsIssueSignatures = ({
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const cmd: Command = txData.command;
|
||||
const cmd: Command = txData.command.issueSignatures;
|
||||
const k = cmd.kind ? kind[cmd.kind] : null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -20,6 +20,10 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
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
|
||||
CYPRESS_VEGA_URL=http://localhost:3008/graphql
|
||||
|
||||
@@ -22,6 +22,8 @@ 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
|
||||
@@ -29,3 +31,4 @@ LC_ALL="en_US.UTF-8"
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
@@ -30,3 +30,4 @@ CYPRESS_FAIRGROUND=false
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
@@ -22,3 +22,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
@@ -22,3 +22,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
@@ -21,3 +21,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
@@ -18,3 +18,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
@@ -23,3 +23,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
@@ -20,3 +20,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
AppFailure,
|
||||
NodeSwitcherDialog,
|
||||
useNodeSwitcherStore,
|
||||
DocsLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ENV } from './config';
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
@@ -109,8 +110,17 @@ const Web3Container = ({
|
||||
store.connectors,
|
||||
store.initialize,
|
||||
]);
|
||||
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
|
||||
useEnvironment();
|
||||
const {
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
ETH_LOCAL_PROVIDER_URL,
|
||||
ETH_WALLET_MNEMONIC,
|
||||
VEGA_ENV,
|
||||
VEGA_URL,
|
||||
VEGA_EXPLORER_URL,
|
||||
CHROME_EXTENSION_URL,
|
||||
MOZILLA_EXTENSION_URL,
|
||||
VEGA_WALLET_URL,
|
||||
} = useEnvironment();
|
||||
useEffect(() => {
|
||||
if (chainId) {
|
||||
return initializeConnectors(
|
||||
@@ -139,10 +149,33 @@ const Web3Container = ({
|
||||
return <SplashLoader />;
|
||||
}
|
||||
|
||||
if (
|
||||
!VEGA_URL ||
|
||||
!VEGA_WALLET_URL ||
|
||||
!VEGA_EXPLORER_URL ||
|
||||
!DocsLinks ||
|
||||
!CHROME_EXTENSION_URL ||
|
||||
!MOZILLA_EXTENSION_URL
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Web3Provider connectors={connectors}>
|
||||
<Web3Connector connectors={connectors} chainId={Number(chainId)}>
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
concepts: DocsLinks?.VEGA_WALLET_CONCEPTS_URL,
|
||||
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
||||
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ContractsProvider>
|
||||
<AppLoader>
|
||||
<BalanceManager>
|
||||
@@ -275,7 +308,7 @@ const AppContainer = () => {
|
||||
<Router>
|
||||
<ScrollToTop />
|
||||
<AppStateProvider>
|
||||
<div className="grid min-h-full text-white">
|
||||
<div className="min-h-full text-white grid">
|
||||
<NodeGuard
|
||||
skeleton={<div>{t('Loading')}</div>}
|
||||
failure={
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
InjectedConnector,
|
||||
SnapConnector,
|
||||
DEFAULT_SNAP_ID,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
export const injected = new InjectedConnector();
|
||||
export const jsonRpc = new JsonRpcConnector();
|
||||
export const injected = new InjectedConnector();
|
||||
export const view = new ViewConnector(urlParams.get('address'));
|
||||
|
||||
export const snap = FLAGS.METAMASK_SNAPS
|
||||
? new SnapConnector(DEFAULT_SNAP_ID)
|
||||
: undefined;
|
||||
|
||||
export const Connectors = {
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap,
|
||||
};
|
||||
|
||||
+21
-38
@@ -1,44 +1,27 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RoundedWrapper } from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import { RoundedWrapper, ShowMore } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const ProposalDescription = ({
|
||||
description,
|
||||
}: {
|
||||
description: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDescription, setShowDescription] = useState(false);
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-description">
|
||||
<CollapsibleToggle
|
||||
toggleState={showDescription}
|
||||
setToggleState={setShowDescription}
|
||||
dataTestId={'proposal-description-toggle'}
|
||||
>
|
||||
<SubHeading title={t('proposalDescription')} />
|
||||
</CollapsibleToggle>
|
||||
|
||||
{showDescription && (
|
||||
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
|
||||
<div className="p-2">
|
||||
<ReactMarkdown
|
||||
className="react-markdown-container"
|
||||
/* Prevents HTML embedded in the description from rendering */
|
||||
skipHtml={true}
|
||||
/* Stops users embedding images which could be used for tracking */
|
||||
disallowedElements={['img']}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{description}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</RoundedWrapper>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<section data-testid="proposal-description">
|
||||
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
|
||||
<div className="p-2">
|
||||
<ShowMore>
|
||||
<ReactMarkdown
|
||||
className="react-markdown-container"
|
||||
/* Prevents HTML embedded in the description from rendering */
|
||||
skipHtml={true}
|
||||
/* Stops users embedding images which could be used for tracking */
|
||||
disallowedElements={['img']}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{description}
|
||||
</ReactMarkdown>
|
||||
</ShowMore>
|
||||
</div>
|
||||
</RoundedWrapper>
|
||||
</section>
|
||||
);
|
||||
|
||||
+138
-152
@@ -15,8 +15,6 @@ import {
|
||||
SettlementAssetInfoPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
Button,
|
||||
CopyWithTooltip,
|
||||
Dialog,
|
||||
@@ -43,6 +41,9 @@ export const useMarketDataDialogStore = create<MarketDataDialogState>(
|
||||
})
|
||||
);
|
||||
|
||||
const marketDataHeaderStyles =
|
||||
'font-alpha calt text-base border-b border-vega-dark-200 mt-2 py-2';
|
||||
|
||||
export const ProposalMarketData = ({
|
||||
marketData,
|
||||
parentMarketData,
|
||||
@@ -76,6 +77,14 @@ export const ProposalMarketData = ({
|
||||
parentTerminationData !== undefined &&
|
||||
isEqual(terminationData, parentTerminationData);
|
||||
|
||||
const showParentPriceMonitoringBounds =
|
||||
parentMarketData?.priceMonitoringSettings?.parameters?.triggers !==
|
||||
undefined &&
|
||||
!isEqual(
|
||||
marketData.priceMonitoringSettings?.parameters?.triggers,
|
||||
parentMarketData?.priceMonitoringSettings?.parameters?.triggers
|
||||
);
|
||||
|
||||
const getSigners = (data: DataSourceDefinition) => {
|
||||
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
|
||||
const signers = data.sourceType.sourceType.signers || [];
|
||||
@@ -108,164 +117,141 @@ export const ProposalMarketData = ({
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-10">
|
||||
<Accordion>
|
||||
<AccordionItem
|
||||
itemId="key-details"
|
||||
title={t('Key details')}
|
||||
content={
|
||||
<KeyDetailsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="instrument"
|
||||
title={t('Instrument')}
|
||||
content={
|
||||
<InstrumentInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{isEqual(
|
||||
getSigners(settlementData),
|
||||
getSigners(terminationData)
|
||||
) ? (
|
||||
<AccordionItem
|
||||
itemId="oracles"
|
||||
title={t('Oracle')}
|
||||
content={
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
parentMarket={
|
||||
isParentSettlementDataEqual
|
||||
? undefined
|
||||
: parentMarketData
|
||||
}
|
||||
/>
|
||||
<h2 className={marketDataHeaderStyles}>{t('Key details')}</h2>
|
||||
<KeyDetailsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Instrument')}</h2>
|
||||
<InstrumentInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
{isEqual(
|
||||
getSigners(settlementData),
|
||||
getSigners(terminationData)
|
||||
) ? (
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>{t('Oracle')}</h2>
|
||||
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
parentMarket={
|
||||
isParentSettlementDataEqual ? undefined : parentMarketData
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Settlement Oracle')}
|
||||
</h2>
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
parentMarket={
|
||||
isParentSettlementDataEqual ? undefined : parentMarketData
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<AccordionItem
|
||||
itemId="settlement-oracle"
|
||||
title={t('Settlement Oracle')}
|
||||
content={
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
parentMarket={
|
||||
isParentSettlementDataEqual
|
||||
? undefined
|
||||
: parentMarketData
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<AccordionItem
|
||||
itemId="termination-oracle"
|
||||
title={t('Termination Oracle')}
|
||||
content={
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="termination"
|
||||
parentMarket={
|
||||
isParentTerminationDataEqual
|
||||
? undefined
|
||||
: parentMarketData
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{/*Note: successor markets will not differ in their settlement*/}
|
||||
{/*assets, so no need to pass in parent market data for comparison.*/}
|
||||
<AccordionItem
|
||||
itemId="settlement-asset"
|
||||
title={t('Settlement asset')}
|
||||
content={<SettlementAssetInfoPanel market={marketData} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="metadata"
|
||||
title={t('Metadata')}
|
||||
content={
|
||||
<MetadataInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-model"
|
||||
title={t('Risk model')}
|
||||
content={
|
||||
<RiskModelInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-parameters"
|
||||
title={t('Risk parameters')}
|
||||
content={
|
||||
<RiskParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-factors"
|
||||
title={t('Risk factors')}
|
||||
content={
|
||||
<RiskFactorsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{(
|
||||
marketData.priceMonitoringSettings?.parameters?.triggers || []
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Termination Oracle')}
|
||||
</h2>
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="termination"
|
||||
parentMarket={
|
||||
isParentTerminationDataEqual ? undefined : parentMarketData
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/*Note: successor markets will not differ in their settlement*/}
|
||||
{/*assets, so no need to pass in parent market data for comparison.*/}
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Settlement assets')}</h2>
|
||||
<SettlementAssetInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Metadata')}</h2>
|
||||
<MetadataInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk model')}</h2>
|
||||
<RiskModelInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk parameters')}</h2>
|
||||
<RiskParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk factors')}</h2>
|
||||
<RiskFactorsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
{showParentPriceMonitoringBounds &&
|
||||
(
|
||||
parentMarketData?.priceMonitoringSettings?.parameters
|
||||
?.triggers || []
|
||||
).map((_, triggerIndex) => (
|
||||
<AccordionItem
|
||||
itemId={`trigger-${triggerIndex}`}
|
||||
title={t(`Price monitoring bounds ${triggerIndex + 1}`)}
|
||||
content={
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t(`Parent price monitoring bounds ${triggerIndex + 1}`)}
|
||||
</h2>
|
||||
|
||||
<div className="text-vega-dark-300 line-through">
|
||||
<PriceMonitoringBoundsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
market={parentMarketData}
|
||||
triggerIndex={triggerIndex}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
<AccordionItem
|
||||
itemId="liqudity-monitoring-parameters"
|
||||
title={t('Liquidity monitoring parameters')}
|
||||
content={
|
||||
<LiquidityMonitoringParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="liquidity-price-range"
|
||||
title={t('Liquidity price range')}
|
||||
content={
|
||||
<LiquidityPriceRangeInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Accordion>
|
||||
|
||||
{(
|
||||
marketData.priceMonitoringSettings?.parameters?.triggers || []
|
||||
).map((_, triggerIndex) => (
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t(`Price monitoring bounds ${triggerIndex + 1}`)}
|
||||
</h2>
|
||||
|
||||
<PriceMonitoringBoundsInfoPanel
|
||||
market={marketData}
|
||||
triggerIndex={triggerIndex}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Liquidity monitoring parameters')}
|
||||
</h2>
|
||||
<LiquidityMonitoringParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Liquidity price range')}
|
||||
</h2>
|
||||
<LiquidityPriceRangeInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletConfig } from '@vegaprotocol/wallet';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { Proposal } from './proposal';
|
||||
@@ -43,11 +44,23 @@ jest.mock('../list-asset', () => ({
|
||||
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||
}));
|
||||
|
||||
const vegaWalletConfig: VegaWalletConfig = {
|
||||
network: 'TESTNET',
|
||||
vegaUrl: 'https://vega.xyz',
|
||||
vegaWalletServiceUrl: 'https://wallet.vega.xyz',
|
||||
links: {
|
||||
explorer: 'explorer',
|
||||
concepts: 'concepts',
|
||||
chromeExtensionUrl: 'chrome',
|
||||
mozillaExtensionUrl: 'mozilla',
|
||||
},
|
||||
};
|
||||
|
||||
const renderComponent = (proposal: ProposalQuery['proposal']) => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletProvider config={vegaWalletConfig}>
|
||||
<Proposal
|
||||
restData={{}}
|
||||
proposal={proposal as ProposalQuery['proposal']}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { VoteButtons } from './vote-buttons';
|
||||
import { VoteState } from './use-user-vote';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
@@ -67,7 +68,7 @@ describe('Vote buttons', () => {
|
||||
disconnect: jest.fn(),
|
||||
selectPubKey: jest.fn(),
|
||||
connector: null,
|
||||
};
|
||||
} as unknown as VegaWalletContextShape;
|
||||
|
||||
render(
|
||||
<AppStateProvider>
|
||||
|
||||
@@ -114,6 +114,7 @@ describe('Raw proposal form', () => {
|
||||
{
|
||||
pubKey,
|
||||
sendTx: mockSendTx,
|
||||
links: { explorer: 'explorer' },
|
||||
} as unknown as VegaWalletContextShape
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import type { PubKey } from '@vegaprotocol/wallet';
|
||||
import type { PubKey, VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import type { VoteValue } from '@vegaprotocol/types';
|
||||
import type { UserVoteQuery } from '../components/vote-details/__generated__/Vote';
|
||||
import { UserVoteDocument } from '../components/vote-details/__generated__/Vote';
|
||||
@@ -21,7 +21,7 @@ export const mockWalletContext = {
|
||||
disconnect: jest.fn(),
|
||||
selectPubKey: jest.fn(),
|
||||
connector: null,
|
||||
};
|
||||
} as unknown as VegaWalletContextShape;
|
||||
|
||||
const mockEthereumConfig = {
|
||||
network_id: '3',
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import type { MarketsQuery } from '@vegaprotocol/markets';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const rowSelector =
|
||||
'[data-testid="tab-open-markets"] .ag-center-cols-container .ag-row';
|
||||
const colInstrumentCode =
|
||||
'[col-id="tradableInstrument.instrument.code"] [data-testid="market-code"]';
|
||||
|
||||
describe('markets all table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.clearLocalStorage().then(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
});
|
||||
});
|
||||
|
||||
it('can see table headers', () => {
|
||||
cy.wait('@Markets');
|
||||
cy.wait('@MarketsData');
|
||||
const headers = [
|
||||
'Market',
|
||||
'Description',
|
||||
'Trading mode',
|
||||
'Status',
|
||||
'Successor market',
|
||||
'Best bid',
|
||||
'Best offer',
|
||||
'Mark price',
|
||||
'Settlement asset',
|
||||
'',
|
||||
];
|
||||
cy.getByTestId('tab-open-markets').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
.get('.ag-header-cell-text')
|
||||
.each(($header, i) => {
|
||||
cy.wrap($header).should('have.text', headers[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('markets tab should be rendered properly', () => {
|
||||
cy.get('[data-testid="Open markets"]').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'active'
|
||||
);
|
||||
cy.get('[data-testid="Proposed markets"]').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.get('[data-testid="Closed markets"]').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
});
|
||||
it('renders markets correctly', () => {
|
||||
// 6001-MARK-035
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find(colInstrumentCode)
|
||||
.should('have.text', 'SOLUSD');
|
||||
|
||||
// 6001-MARK-073
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[title="Future"]')
|
||||
.should('have.text', 'Futr');
|
||||
|
||||
// 6001-MARK-036
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="tradableInstrument.instrument.name"]')
|
||||
.should('have.text', 'SUSPENDED MARKET');
|
||||
|
||||
// 6001-MARK-037
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="tradingMode"]')
|
||||
.should('have.text', 'Continuous');
|
||||
|
||||
// 6001-MARK-038
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="state"]')
|
||||
.should('have.text', 'Active');
|
||||
|
||||
// 6001-MARK-039
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="data.bestBidPrice"]')
|
||||
.should('have.text', '0.00');
|
||||
|
||||
// 6001-MARK-040
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="data.bestOfferPrice"]')
|
||||
.should('have.text', '0.00');
|
||||
|
||||
// 6001-MARK-041
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="data.markPrice"]')
|
||||
.should('have.text', '84.41');
|
||||
|
||||
// 6001-MARK-042
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find(
|
||||
'[col-id="tradableInstrument.instrument.product.settlementAsset.symbol"]'
|
||||
)
|
||||
.should('have.text', 'XYZalpha');
|
||||
|
||||
// 6001-MARK-043
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find(
|
||||
'[col-id="tradableInstrument.instrument.product.settlementAsset.symbol"] button'
|
||||
)
|
||||
.click();
|
||||
cy.getByTestId('dialog-title').should('have.text', 'Asset details - tEURO');
|
||||
cy.getByTestId('close-asset-details-dialog').click();
|
||||
});
|
||||
|
||||
it('can open row actions', () => {
|
||||
// 6001-MARK-044
|
||||
cy.get('.ag-pinned-right-cols-container')
|
||||
.find('[col-id="market-actions"]')
|
||||
.first()
|
||||
.find('button')
|
||||
.click();
|
||||
|
||||
// 6001-MARK-045
|
||||
const dropdownContent = '[data-testid="market-actions-content"]';
|
||||
const dropdownContentItem = '[role="menuitem"]';
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(0)
|
||||
// Cannot click the copy button as it falls back to window.prompt, blocking the test.
|
||||
.should('have.text', 'Copy Market ID');
|
||||
|
||||
// 6001-MARK-046
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(1)
|
||||
.find('a')
|
||||
.then(($el) => {
|
||||
const href = $el.attr('href');
|
||||
expect(/\/markets\/market-1/.test(href || '')).to.equal(true);
|
||||
})
|
||||
.should('have.text', 'View on Explorer');
|
||||
|
||||
// 6001-MARK-047
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(2)
|
||||
.should('have.text', 'View settlement asset details');
|
||||
cy.getByTestId('market-actions-content').click();
|
||||
});
|
||||
|
||||
it('able to open and sort full market list - market page', () => {
|
||||
// 6001-MARK-064
|
||||
const ExpectedSortedMarkets = [
|
||||
'AAPL.MF21',
|
||||
'BTCUSD.MF21',
|
||||
'ETHBTC.QM21',
|
||||
'SOLUSD',
|
||||
];
|
||||
cy.get('[data-testid="Open markets"]').click({ force: true });
|
||||
cy.url().should('eq', Cypress.config('baseUrl') + '/#/markets/all');
|
||||
cy.contains('AAPL.MF21').should('be.visible');
|
||||
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
|
||||
for (let i = 0; i < ExpectedSortedMarkets.length; i++) {
|
||||
cy.get(`[row-index=${i}]`)
|
||||
.find(colInstrumentCode)
|
||||
.should('have.text', ExpectedSortedMarkets[i]);
|
||||
}
|
||||
});
|
||||
|
||||
it('can drag and drop columns', () => {
|
||||
// 6001-MARK-065
|
||||
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
|
||||
cy.get(colInstrumentCode)
|
||||
.realMouseDown()
|
||||
.realMouseMove(700, 15)
|
||||
.realMouseUp();
|
||||
cy.get(colInstrumentCode).should(($element) => {
|
||||
const attributeValue = $element.attr('aria-colindex');
|
||||
expect(attributeValue).not.to.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no open markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
const markets: MarketsQuery = {};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Markets', markets);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
});
|
||||
|
||||
it.skip('can see no markets message', () => {
|
||||
// 6001-MARK-048
|
||||
cy.getByTestId('tab-open-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
@@ -1,239 +0,0 @@
|
||||
import { aliasGQLQuery, checkSorting } from '@vegaprotocol/cypress';
|
||||
import type { ProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
|
||||
const rowSelector =
|
||||
'[data-testid="tab-proposed-markets"] .ag-center-cols-container .ag-row';
|
||||
const colMarketId = '[col-id="market"] [data-testid="market-code"]';
|
||||
|
||||
describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
|
||||
it('can see table headers', () => {
|
||||
const headers = [
|
||||
'Market',
|
||||
'Description',
|
||||
'Settlement asset',
|
||||
'State',
|
||||
'Parent market',
|
||||
'Voting',
|
||||
'Closing date',
|
||||
'Enactment date',
|
||||
'',
|
||||
];
|
||||
cy.getByTestId('tab-proposed-markets').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
.get('.ag-header-cell-text')
|
||||
.each(($header, i) => {
|
||||
cy.wrap($header).should('have.text', headers[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders markets correctly', () => {
|
||||
// 6001-MARK-049
|
||||
cy.get(rowSelector).first().find(colMarketId).should('have.text', 'ETHUSD');
|
||||
|
||||
// 6001-MARK-050
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="description"]')
|
||||
.should('have.text', 'ETHUSD');
|
||||
|
||||
// 6001-MARK-074
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[title="Future"]')
|
||||
.should('have.text', 'Futr');
|
||||
|
||||
// 6001-MARK-051
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="asset"]')
|
||||
.should('have.text', 'tDAI TEST');
|
||||
|
||||
// 6001-MARK-052
|
||||
// 6001-MARK-053
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="state"]')
|
||||
.should('have.text', 'Open');
|
||||
|
||||
// 6001-MARK-054
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="voting"]')
|
||||
.should('have.text', '');
|
||||
|
||||
// 6001-MARK-056
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="closing-date"]')
|
||||
.should('not.be.empty');
|
||||
|
||||
// 6001-MARK-057
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="enactment-date"]')
|
||||
.should('not.be.empty');
|
||||
});
|
||||
|
||||
it('can open row actions', () => {
|
||||
// 6001-MARK-058
|
||||
cy.get('.ag-pinned-right-cols-container')
|
||||
.find('[col-id="proposal-actions"]')
|
||||
.first()
|
||||
.find('button')
|
||||
.click();
|
||||
|
||||
const dropdownContent = '[data-testid="proposal-actions-content"]';
|
||||
const dropdownContentItem = '[role="menuitem"]';
|
||||
|
||||
// 6001-MARK-059
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(0)
|
||||
.find('a')
|
||||
.should('have.text', 'View proposal')
|
||||
.and(
|
||||
'have.attr',
|
||||
'href',
|
||||
`${Cypress.env(
|
||||
'VEGA_TOKEN_URL'
|
||||
)}/proposals/e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829`
|
||||
);
|
||||
});
|
||||
|
||||
// 6001-MARK-060
|
||||
it('can see proposed market link', () => {
|
||||
cy.getByTestId('tab-proposed-markets')
|
||||
.find('[data-testid="external-link"]')
|
||||
.should('have.length', 11)
|
||||
.last()
|
||||
.should('have.text', 'Propose a new market')
|
||||
.and(
|
||||
'have.attr',
|
||||
'href',
|
||||
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
|
||||
);
|
||||
});
|
||||
it('proposed markets tab should be sorted properly', () => {
|
||||
// 6001-MARK-062
|
||||
cy.get('[data-testid="Proposed markets"]').click({ force: true });
|
||||
const marketColDefault = [
|
||||
'ETHUSD',
|
||||
'LINKUSD',
|
||||
'ETHUSD',
|
||||
'ETHDAI.MF21',
|
||||
'AAPL.MF21',
|
||||
'BTCUSD.MF21',
|
||||
'TSLA.QM21',
|
||||
'AAVEDAI.MF21',
|
||||
'ETHBTC.QM21',
|
||||
'UNIDAI.MF21',
|
||||
];
|
||||
const marketColAsc = [
|
||||
'AAPL.MF21',
|
||||
'AAVEDAI.MF21',
|
||||
'BTCUSD.MF21',
|
||||
'ETHBTC.QM21',
|
||||
'ETHDAI.MF21',
|
||||
'ETHUSD',
|
||||
'ETHUSD',
|
||||
'LINKUSD',
|
||||
'TSLA.QM21',
|
||||
'UNIDAI.MF21',
|
||||
];
|
||||
const marketColDesc = [
|
||||
'UNIDAI.MF21',
|
||||
'TSLA.QM21',
|
||||
'LINKUSD',
|
||||
'ETHUSD',
|
||||
'ETHUSD',
|
||||
'ETHDAI.MF21',
|
||||
'ETHBTC.QM21',
|
||||
'BTCUSD.MF21',
|
||||
'AAVEDAI.MF21',
|
||||
'AAPL.MF21',
|
||||
];
|
||||
checkSorting(
|
||||
'market',
|
||||
marketColDefault,
|
||||
marketColAsc,
|
||||
marketColDesc,
|
||||
' [data-testid="market-code"]'
|
||||
);
|
||||
|
||||
const stateColDefault = [
|
||||
'Open',
|
||||
'Passed',
|
||||
'Waiting for Node Vote',
|
||||
'Open',
|
||||
'Passed',
|
||||
'Open',
|
||||
'Passed',
|
||||
'Open',
|
||||
'Waiting for Node Vote',
|
||||
'Open',
|
||||
];
|
||||
const stateColAsc = [
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
'Passed',
|
||||
'Passed',
|
||||
'Passed',
|
||||
'Waiting for Node Vote',
|
||||
'Waiting for Node Vote',
|
||||
];
|
||||
const stateColDesc = [
|
||||
'Waiting for Node Vote',
|
||||
'Waiting for Node Vote',
|
||||
'Passed',
|
||||
'Passed',
|
||||
'Passed',
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
];
|
||||
checkSorting('state', stateColDefault, stateColAsc, stateColDesc);
|
||||
});
|
||||
|
||||
it('can drag and drop columns', () => {
|
||||
// 6001-MARK-063
|
||||
cy.get(colMarketId).realMouseDown().realMouseMove(700, 15).realMouseUp();
|
||||
cy.get(colMarketId).should(($element) => {
|
||||
const attributeValue = $element.attr('aria-colindex');
|
||||
expect(attributeValue).not.to.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no markets proposed', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
const proposal: ProposalsListQuery = {};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'ProposalsList', proposal);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
});
|
||||
|
||||
it.skip('can see no markets message', () => {
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
|
||||
// 6001-MARK-061
|
||||
cy.getByTestId('tab-proposed-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { testOrderSubmission } from '../support/order-validation';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { createOrder } from '../support/create-order';
|
||||
|
||||
describe.skip('must submit order', { tags: '@smoke' }, () => {
|
||||
// 7002-SORD-039
|
||||
before(() => {
|
||||
cy.setVegaWallet();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
|
||||
it('successfully places market buy order', () => {
|
||||
// 7002-SORD-010
|
||||
// 0003-WTXN-012
|
||||
// 0003-WTXN-003
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
size: '100',
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order);
|
||||
});
|
||||
|
||||
it('successfully places market sell order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order);
|
||||
});
|
||||
|
||||
it('successfully places limit buy order', () => {
|
||||
// 7002-SORD-017
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '200',
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, { price: '20000000' });
|
||||
});
|
||||
|
||||
it('successfully places limit sell order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GFN,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '50000',
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, { price: '5000000000' });
|
||||
});
|
||||
|
||||
it('successfully places GTT limit buy order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||
size: '100',
|
||||
price: '1.00',
|
||||
expiresAt: expiresAt.toISOString().substring(0, 16),
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
};
|
||||
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, {
|
||||
price: '100000',
|
||||
expiresAt:
|
||||
new Date(order.expiresAt as string).getTime().toString() + '000000',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -64,7 +64,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId(orderSizeField).clear().type('1');
|
||||
cy.getByTestId(orderPriceField).clear().type('1.123456');
|
||||
cy.getByTestId(placeOrderBtn).click();
|
||||
cy.getByTestId('deal-ticket-error-message-price-limit').should(
|
||||
cy.getByTestId('deal-ticket-error-message-price').should(
|
||||
'have.text',
|
||||
'Price accepts up to 5 decimal places'
|
||||
);
|
||||
@@ -87,7 +87,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId(orderSizeField).clear().type('1.234');
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId('deal-ticket-error-message-size-market').should(
|
||||
cy.getByTestId('deal-ticket-error-message-size').should(
|
||||
'have.text',
|
||||
'Size must be whole numbers for this market'
|
||||
);
|
||||
@@ -96,7 +96,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
it('must warn if order size is set to 0', function () {
|
||||
cy.getByTestId(orderSizeField).clear().type('0');
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId('deal-ticket-error-message-size-market').should(
|
||||
cy.getByTestId('deal-ticket-error-message-size').should(
|
||||
'have.text',
|
||||
'Size cannot be lower than 1'
|
||||
);
|
||||
|
||||
@@ -12,6 +12,8 @@ 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
|
||||
@@ -19,6 +21,7 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
@@ -23,6 +23,7 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
|
||||
@@ -21,6 +21,7 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
|
||||
@@ -23,6 +23,7 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
@@ -23,6 +23,7 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
@@ -21,3 +21,4 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
@@ -22,6 +22,7 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
@@ -23,6 +23,7 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import {
|
||||
AppFailure,
|
||||
DocsLinks,
|
||||
NetworkLoader,
|
||||
NodeGuard,
|
||||
useEnvironment,
|
||||
@@ -17,16 +18,32 @@ export const DynamicLoader = dynamic(() => import('../preloader/preloader'), {
|
||||
});
|
||||
|
||||
export const AppLoader = ({ children }: { children: ReactNode }) => {
|
||||
const { error, VEGA_URL, MAINTENANCE_PAGE } = useEnvironment((store) => ({
|
||||
error: store.error,
|
||||
VEGA_URL: store.VEGA_URL,
|
||||
MAINTENANCE_PAGE: store.MAINTENANCE_PAGE,
|
||||
}));
|
||||
const {
|
||||
error,
|
||||
VEGA_URL,
|
||||
VEGA_ENV,
|
||||
VEGA_WALLET_URL,
|
||||
VEGA_EXPLORER_URL,
|
||||
MAINTENANCE_PAGE,
|
||||
MOZILLA_EXTENSION_URL,
|
||||
CHROME_EXTENSION_URL,
|
||||
} = useEnvironment();
|
||||
|
||||
if (MAINTENANCE_PAGE) {
|
||||
return <MaintenancePage />;
|
||||
}
|
||||
|
||||
if (
|
||||
!VEGA_URL ||
|
||||
!VEGA_WALLET_URL ||
|
||||
!VEGA_EXPLORER_URL ||
|
||||
!CHROME_EXTENSION_URL ||
|
||||
!MOZILLA_EXTENSION_URL ||
|
||||
!DocsLinks
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<NetworkLoader
|
||||
cache={cacheConfig}
|
||||
@@ -40,7 +57,21 @@ export const AppLoader = ({ children }: { children: ReactNode }) => {
|
||||
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||
>
|
||||
<Web3Provider>
|
||||
<VegaWalletProvider>{children}</VegaWalletProvider>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
concepts: DocsLinks.VEGA_WALLET_CONCEPTS_URL,
|
||||
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
||||
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</VegaWalletProvider>
|
||||
</Web3Provider>
|
||||
</NodeGuard>
|
||||
</NetworkLoader>
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
} from './sidebar';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
|
||||
jest.mock('../node-health', () => ({
|
||||
NodeHealthContainer: () => <span data-testid="node-health" />,
|
||||
@@ -31,16 +32,20 @@ jest.mock('../welcome-dialog', () => ({
|
||||
GetStarted: () => <div data-testid="get-started" />,
|
||||
}));
|
||||
|
||||
const walletContext = {
|
||||
pubKeys: [{ publicKey: 'pubkey' }],
|
||||
} as VegaWalletContextShape;
|
||||
|
||||
describe('Sidebar', () => {
|
||||
it.each(['/markets/all', '/portfolio'])(
|
||||
'does not render ticket and info',
|
||||
(path) => {
|
||||
render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={[path]}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||
@@ -58,11 +63,11 @@ describe('Sidebar', () => {
|
||||
|
||||
it('renders ticket and info on market pages', () => {
|
||||
render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||
@@ -79,11 +84,11 @@ describe('Sidebar', () => {
|
||||
|
||||
it('renders selected state', async () => {
|
||||
render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
const settingsButton = screen.getByTestId(ViewType.Settings);
|
||||
@@ -107,13 +112,13 @@ describe('Sidebar', () => {
|
||||
describe('SidebarContent', () => {
|
||||
it('renders the correct content', () => {
|
||||
const { container } = render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Routes>
|
||||
<Route path="/markets/:marketId" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
@@ -133,13 +138,13 @@ describe('SidebarContent', () => {
|
||||
|
||||
it('closes sidebar if market id is required but not present', () => {
|
||||
const { container } = render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/portfolio']}>
|
||||
<Routes>
|
||||
<Route path="/portfolio" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
|
||||
@@ -36,6 +36,6 @@ export const StopOrdersContainer = () => {
|
||||
|
||||
const useStopOrdersStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_fills_store',
|
||||
name: 'vega_stop_orders_store',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -29,6 +29,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
const { CHROME_EXTENSION_URL, MOZILLA_EXTENSION_URL } = useEnvironment();
|
||||
const navigate = useNavigate();
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
@@ -46,7 +47,13 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
openVegaWalletDialog();
|
||||
};
|
||||
if (step === OnboardingStep.ONBOARDING_WALLET_STEP) {
|
||||
return <GetWalletButton className="justify-between" />;
|
||||
return (
|
||||
<GetWalletButton
|
||||
className="justify-between"
|
||||
chromeExtensionUrl={CHROME_EXTENSION_URL}
|
||||
mozillaExtensionUrl={MOZILLA_EXTENSION_URL}
|
||||
/>
|
||||
);
|
||||
} else if (step === OnboardingStep.ONBOARDING_CONNECT_STEP) {
|
||||
buttonText = t('Connect');
|
||||
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
|
||||
@@ -105,38 +112,29 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
{lead && <h2>{lead}</h2>}
|
||||
<h3 className="text-lg">{t('Get started')}</h3>
|
||||
<div>
|
||||
<ul className="list-inside -ml-5" role="list">
|
||||
<li className="flex">
|
||||
<div className="w-5">
|
||||
{currentStep > OnboardingStep.ONBOARDING_WALLET_STEP && (
|
||||
<VegaIcon name={VegaIconNames.TICK} size={20} />
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-1">1. {t('Get a Vega wallet')}</div>
|
||||
</li>
|
||||
<li className="flex">
|
||||
<div className="w-5">
|
||||
{(currentStep > OnboardingStep.ONBOARDING_CONNECT_STEP ||
|
||||
pubKey) && <VegaIcon name={VegaIconNames.TICK} size={20} />}
|
||||
</div>
|
||||
<div className="ml-1">2. {t('Connect')}</div>
|
||||
</li>
|
||||
<li className="flex">
|
||||
<div className="w-5">
|
||||
{currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP && (
|
||||
<VegaIcon name={VegaIconNames.TICK} size={20} />
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-1">3. {t('Deposit funds')}</div>
|
||||
</li>
|
||||
<li className="flex">
|
||||
<div className="w-5">
|
||||
{currentStep > OnboardingStep.ONBOARDING_ORDER_STEP && (
|
||||
<VegaIcon name={VegaIconNames.TICK} size={20} />
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-1">4. {t('Open a position')}</div>
|
||||
</li>
|
||||
<ul className="list-none">
|
||||
<Step
|
||||
step={1}
|
||||
text={t('Get a Vega wallet')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_WALLET_STEP}
|
||||
/>
|
||||
<Step
|
||||
step={2}
|
||||
text={t('Connect')}
|
||||
complete={Boolean(
|
||||
currentStep > OnboardingStep.ONBOARDING_CONNECT_STEP || pubKey
|
||||
)}
|
||||
/>
|
||||
<Step
|
||||
step={3}
|
||||
text={t('Deposit funds')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP}
|
||||
/>
|
||||
<Step
|
||||
step={4}
|
||||
text={t('Open a position')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_ORDER_STEP}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
@@ -165,7 +163,7 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<p className="text-sm mb-1">
|
||||
<p className="mb-1 text-sm">
|
||||
You need a{' '}
|
||||
<ExternalLink href="https://vega.xyz/wallet">
|
||||
Vega wallet
|
||||
@@ -186,3 +184,34 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Step = ({
|
||||
step,
|
||||
text,
|
||||
complete,
|
||||
}: {
|
||||
step: number;
|
||||
text: string;
|
||||
complete: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<li
|
||||
className={classNames('flex', {
|
||||
'text-vega-clight-200 dark:text-vega-cdark-200': complete,
|
||||
})}
|
||||
>
|
||||
<div className="flex justify-center w-5">
|
||||
{complete ? <Tick /> : <span>{step}.</span>}
|
||||
</div>
|
||||
<div className="ml-1">{text}</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
const Tick = () => {
|
||||
return (
|
||||
<span className="relative right-[2px]">
|
||||
<VegaIcon name={VegaIconNames.TICK} size={18} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
InjectedConnector,
|
||||
SnapConnector,
|
||||
DEFAULT_SNAP_ID,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
export const jsonRpc = new JsonRpcConnector();
|
||||
@@ -15,8 +18,13 @@ if (typeof window !== 'undefined') {
|
||||
view = new ViewConnector();
|
||||
}
|
||||
|
||||
export const snap = FLAGS.METAMASK_SNAPS
|
||||
? new SnapConnector(DEFAULT_SNAP_ID)
|
||||
: undefined;
|
||||
|
||||
export const Connectors = {
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap,
|
||||
};
|
||||
|
||||
@@ -15,6 +15,10 @@ body,
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
@apply tracking-tighter;
|
||||
}
|
||||
|
||||
.text-default {
|
||||
@apply text-vega-clight-50 dark:text-vega-cdark-50;
|
||||
}
|
||||
@@ -60,6 +64,10 @@ html.dark {
|
||||
|
||||
html [data-theme='dark'],
|
||||
html [data-theme='light'] {
|
||||
/* fonts */
|
||||
--pennant-font-family-base: theme(fontFamily.alpha);
|
||||
--pennant-font-family-monospace: theme(fontFamily.mono);
|
||||
|
||||
/* sell candles only use stroke as the candle is solid (without border) */
|
||||
--pennant-color-sell-stroke: theme(colors.market.red.DEFAULT);
|
||||
|
||||
@@ -147,7 +155,7 @@ html [data-theme='dark'] {
|
||||
}
|
||||
|
||||
.vega-ag-grid .ag-header-row {
|
||||
@apply font-alpha font-normal;
|
||||
@apply font-normal font-alpha;
|
||||
}
|
||||
|
||||
/* Light variables */
|
||||
@@ -209,3 +217,15 @@ html [data-theme='dark'] {
|
||||
box-shadow: inset 0 0 6px rgb(0 0 0 / 30%);
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
@@ -153,8 +153,10 @@ export function waitForProposal(id: string): Promise<{ id: string }> {
|
||||
try {
|
||||
const res = await getProposal(id);
|
||||
if (
|
||||
res.proposal !== null &&
|
||||
res.proposal.state === Schema.ProposalState.STATE_OPEN
|
||||
(res.proposal !== null &&
|
||||
res.proposal.state === Schema.ProposalState.STATE_OPEN) ||
|
||||
res.proposal.state ===
|
||||
Schema.ProposalState.STATE_WAITING_FOR_NODE_VOTE
|
||||
) {
|
||||
clearInterval(interval);
|
||||
resolve(res.proposal);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { Control } from 'react-hook-form';
|
||||
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
|
||||
import { DealTicketMarketAmount } from './deal-ticket-market-amount';
|
||||
import { DealTicketLimitAmount } from './deal-ticket-limit-amount';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { OrderFormValues } from '../../hooks/use-form-values';
|
||||
|
||||
export interface DealTicketAmountProps {
|
||||
control: Control<OrderFormValues>;
|
||||
type: Schema.OrderType;
|
||||
marketData: StaticMarketData;
|
||||
marketPrice?: string;
|
||||
market: Market;
|
||||
sizeError?: string;
|
||||
priceError?: string;
|
||||
}
|
||||
|
||||
export const DealTicketAmount = ({
|
||||
type,
|
||||
marketData,
|
||||
marketPrice,
|
||||
...props
|
||||
}: DealTicketAmountProps) => {
|
||||
switch (type) {
|
||||
case Schema.OrderType.TYPE_MARKET:
|
||||
return (
|
||||
<DealTicketMarketAmount
|
||||
{...props}
|
||||
marketData={marketData}
|
||||
marketPrice={marketPrice}
|
||||
/>
|
||||
);
|
||||
case Schema.OrderType.TYPE_LIMIT:
|
||||
return <DealTicketLimitAmount {...props} />;
|
||||
default: {
|
||||
throw new Error('Invalid ticket type ' + type);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,120 +0,0 @@
|
||||
import {
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
TradingInputError,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||
import { Controller } from 'react-hook-form';
|
||||
|
||||
export type DealTicketLimitAmountProps = Omit<
|
||||
DealTicketAmountProps,
|
||||
'marketData' | 'type'
|
||||
>;
|
||||
|
||||
export const DealTicketLimitAmount = ({
|
||||
control,
|
||||
market,
|
||||
sizeError,
|
||||
priceError,
|
||||
}: DealTicketLimitAmountProps) => {
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
const renderError = () => {
|
||||
if (sizeError) {
|
||||
return (
|
||||
<TradingInputError testId="deal-ticket-error-message-size-limit">
|
||||
{sizeError}
|
||||
</TradingInputError>
|
||||
);
|
||||
}
|
||||
|
||||
if (priceError) {
|
||||
return (
|
||||
<TradingInputError testId="deal-ticket-error-message-price-limit">
|
||||
{priceError}
|
||||
</TradingInputError>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-1">
|
||||
<TradingFormGroup
|
||||
label={t('Size')}
|
||||
labelFor="input-order-size-limit"
|
||||
className="!mb-0"
|
||||
>
|
||||
<Controller
|
||||
name="size"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need to provide a size'),
|
||||
min: {
|
||||
value: sizeStep,
|
||||
message: t('Size cannot be lower than ' + sizeStep),
|
||||
},
|
||||
validate: validateAmount(sizeStep, 'Size'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<TradingInput
|
||||
id="input-order-size-limit"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
data-testid="order-size"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
hasError={!!fieldState.error}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
</div>
|
||||
<div className="pt-5 leading-10">@</div>
|
||||
<div className="flex-1">
|
||||
<TradingFormGroup
|
||||
labelFor="input-price-quote"
|
||||
label={t(`Price (${quoteName})`)}
|
||||
labelAlign="right"
|
||||
className="!mb-0"
|
||||
>
|
||||
<Controller
|
||||
name="price"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need provide a price'),
|
||||
min: {
|
||||
value: priceStep,
|
||||
message: t('Price cannot be lower than ' + priceStep),
|
||||
},
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<TradingInput
|
||||
id="input-price-quote"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
data-testid="order-price"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
hasError={!!fieldState.error}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
{renderError()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,102 +0,0 @@
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
toDecimal,
|
||||
validateAmount,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
TradingInput,
|
||||
TradingInputError,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { isMarketInAuction } from '@vegaprotocol/markets';
|
||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export type DealTicketMarketAmountProps = Omit<DealTicketAmountProps, 'type'>;
|
||||
|
||||
export const DealTicketMarketAmount = ({
|
||||
control,
|
||||
market,
|
||||
marketData,
|
||||
marketPrice,
|
||||
sizeError,
|
||||
}: DealTicketMarketAmountProps) => {
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const price = marketPrice;
|
||||
|
||||
const priceFormatted = price
|
||||
? addDecimalsFormatNumber(price, market.decimalPlaces)
|
||||
: undefined;
|
||||
|
||||
const inAuction = isMarketInAuction(marketData.marketTradingMode);
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="mb-2 text-xs">{t('Size')}</div>
|
||||
<Controller
|
||||
name="size"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need to provide a size'),
|
||||
min: {
|
||||
value: sizeStep,
|
||||
message: t('Size cannot be lower than ' + sizeStep),
|
||||
},
|
||||
validate: validateAmount(sizeStep, 'Size'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<TradingInput
|
||||
id="input-order-size-market"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
data-testid="order-size"
|
||||
hasError={!!fieldState.error}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-5 leading-10">@</div>
|
||||
<div className="flex-1 text-sm text-right">
|
||||
{inAuction && (
|
||||
<Tooltip
|
||||
description={t(
|
||||
'This market is in auction. The uncrossing price is an indication of what the price is expected to be when the auction ends.'
|
||||
)}
|
||||
>
|
||||
<div className="mb-2">{t(`Indicative price`)}</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div
|
||||
data-testid="last-price"
|
||||
className={classNames('leading-10', { 'pt-5': !inAuction })}
|
||||
>
|
||||
{priceFormatted && quoteName ? (
|
||||
<>
|
||||
~{priceFormatted} {quoteName}
|
||||
</>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{sizeError && (
|
||||
<TradingInputError
|
||||
intent="danger"
|
||||
testId="deal-ticket-error-message-size-market"
|
||||
>
|
||||
{sizeError}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -72,6 +72,7 @@ const timeInForce = 'order-tif';
|
||||
const sizeErrorMessage = 'stop-order-error-message-size';
|
||||
const priceErrorMessage = 'stop-order-error-message-price';
|
||||
const triggerPriceErrorMessage = 'stop-order-error-message-trigger-price';
|
||||
const triggerPriceWarningMessage = 'stop-order-warning-message-trigger-price';
|
||||
const triggerTrailingPercentOffsetErrorMessage =
|
||||
'stop-order-error-message-trigger-trailing-percent-offset';
|
||||
|
||||
@@ -114,14 +115,6 @@ describe('StopOrder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should display trigger price as price for market type order', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '10');
|
||||
expect(screen.getByTestId('price')).toHaveTextContent('10.0');
|
||||
});
|
||||
|
||||
it('should use local storage state for initial values', async () => {
|
||||
const values: Partial<StopOrderFormValues> = {
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
@@ -203,8 +196,8 @@ describe('StopOrder', () => {
|
||||
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
// price error message should not show if size has error
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
// expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
// await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
@@ -249,10 +242,17 @@ describe('StopOrder', () => {
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.001');
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
// clear and fill using value causing immediate trigger
|
||||
await userEvent.clear(screen.getByTestId(triggerPriceInput));
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.01');
|
||||
expect(screen.queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
expect(
|
||||
screen.queryByTestId(triggerPriceWarningMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// change to correct value
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '2');
|
||||
expect(screen.queryByTestId(triggerPriceWarningMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates trigger trailing percentage offset field', async () => {
|
||||
|
||||
@@ -2,24 +2,26 @@ import { useRef, useCallback, useEffect } from 'react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { StopOrdersSubmission } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
formatNumber,
|
||||
formatForInput,
|
||||
removeDecimal,
|
||||
toDecimal,
|
||||
validateAmount,
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { Control, UseFormWatch } from 'react-hook-form';
|
||||
import { useForm, Controller, useController } from 'react-hook-form';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
TradingRadio,
|
||||
TradingRadioGroup,
|
||||
TradingInput,
|
||||
TradingCheckbox,
|
||||
TradingFormGroup,
|
||||
TradingInputError,
|
||||
TradingSelect,
|
||||
TradingRadio as Radio,
|
||||
TradingRadioGroup as RadioGroup,
|
||||
TradingInput as Input,
|
||||
TradingCheckbox as Checkbox,
|
||||
TradingFormGroup as FormGroup,
|
||||
TradingInputError as InputError,
|
||||
TradingSelect as Select,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { getDerivedPrice, type Market } from '@vegaprotocol/markets';
|
||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ExpirySelector } from './expiry-selector';
|
||||
import { SideSelector } from './side-selector';
|
||||
@@ -34,10 +36,10 @@ import { TypeToggle } from './type-selector';
|
||||
import {
|
||||
useDealTicketFormValues,
|
||||
DealTicketType,
|
||||
type StopOrderFormValues,
|
||||
dealTicketTypeToOrderType,
|
||||
isStopOrderType,
|
||||
} from '../../hooks/use-form-values';
|
||||
import type { StopOrderFormValues } from '../../hooks/use-form-values';
|
||||
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-submission';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
||||
@@ -49,6 +51,8 @@ export interface StopOrderProps {
|
||||
submit: (order: StopOrdersSubmission) => void;
|
||||
}
|
||||
|
||||
const trailingPercentOffsetStep = '0.1';
|
||||
|
||||
const getDefaultValues = (
|
||||
type: Schema.OrderType,
|
||||
storedValues?: Partial<StopOrderFormValues>
|
||||
@@ -62,9 +66,426 @@ const getDefaultValues = (
|
||||
expire: false,
|
||||
expiryStrategy: Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT,
|
||||
size: '0',
|
||||
oco: false,
|
||||
ocoType: type,
|
||||
ocoTimeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
ocoTriggerType: 'price',
|
||||
ocoSize: '0',
|
||||
...storedValues,
|
||||
});
|
||||
|
||||
const Trigger = ({
|
||||
control,
|
||||
watch,
|
||||
priceStep,
|
||||
assetSymbol,
|
||||
oco,
|
||||
marketPrice,
|
||||
decimalPlaces,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
watch: UseFormWatch<StopOrderFormValues>;
|
||||
priceStep: string;
|
||||
assetSymbol: string;
|
||||
oco?: boolean;
|
||||
marketPrice?: string | null;
|
||||
decimalPlaces: number;
|
||||
}) => {
|
||||
const triggerType = watch(oco ? 'ocoTriggerType' : 'triggerType');
|
||||
const triggerDirection = watch('triggerDirection');
|
||||
const isPriceTrigger = triggerType === 'price';
|
||||
return (
|
||||
<FormGroup label={t('Trigger')} labelFor="">
|
||||
<Controller
|
||||
name="triggerDirection"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const { value, onChange } = field;
|
||||
return (
|
||||
<RadioGroup
|
||||
name="triggerDirection"
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
className="mb-2"
|
||||
>
|
||||
<Radio
|
||||
value={
|
||||
oco
|
||||
? Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
: Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
}
|
||||
id={`triggerDirection-risesAbove${oco ? '-oco' : ''}`}
|
||||
label={'Rises above'}
|
||||
/>
|
||||
<Radio
|
||||
value={
|
||||
!oco
|
||||
? Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
: Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
}
|
||||
id={`triggerDirection-fallsBelow${oco ? '-oco' : ''}`}
|
||||
label={'Falls below'}
|
||||
/>
|
||||
</RadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{isPriceTrigger && (
|
||||
<div className="mb-2">
|
||||
<Controller
|
||||
name={oco ? 'ocoTriggerPrice' : 'triggerPrice'}
|
||||
rules={{
|
||||
required: t('You need provide a price'),
|
||||
min: {
|
||||
value: priceStep,
|
||||
message: t('Price cannot be lower than ' + priceStep),
|
||||
},
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
let triggerWarning = false;
|
||||
|
||||
if (marketPrice && value) {
|
||||
const condition =
|
||||
(!oco &&
|
||||
triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_RISES_ABOVE) ||
|
||||
(oco &&
|
||||
triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_FALLS_BELOW)
|
||||
? '>'
|
||||
: '<';
|
||||
const diff =
|
||||
BigInt(marketPrice) -
|
||||
BigInt(removeDecimal(value, decimalPlaces));
|
||||
if (
|
||||
(condition === '>' && diff > 0) ||
|
||||
(condition === '<' && diff < 0)
|
||||
) {
|
||||
triggerWarning = true;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<Input
|
||||
data-testid={`triggerPrice${oco ? '-oco' : ''}`}
|
||||
type="number"
|
||||
step={priceStep}
|
||||
appendElement={assetSymbol}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
{fieldState.error && (
|
||||
<InputError
|
||||
testId={`stop-order-error-message-trigger-price${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
{!fieldState.error && triggerWarning && (
|
||||
<InputError
|
||||
intent="warning"
|
||||
testId={`stop-order-warning-message-trigger-price${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
>
|
||||
{t('Stop order will be triggered immediately')}
|
||||
</InputError>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!isPriceTrigger && (
|
||||
<div className="mb-2">
|
||||
<Controller
|
||||
name={
|
||||
oco
|
||||
? 'ocoTriggerTrailingPercentOffset'
|
||||
: 'triggerTrailingPercentOffset'
|
||||
}
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need provide a trailing percent offset'),
|
||||
min: {
|
||||
value: trailingPercentOffsetStep,
|
||||
message: t(
|
||||
'Trailing percent offset cannot be lower than ' +
|
||||
trailingPercentOffsetStep
|
||||
),
|
||||
},
|
||||
max: {
|
||||
value: '99.9',
|
||||
message: t(
|
||||
'Trailing percent offset cannot be higher than 99.9'
|
||||
),
|
||||
},
|
||||
validate: validateAmount(
|
||||
trailingPercentOffsetStep,
|
||||
'Trailing percentage offset'
|
||||
),
|
||||
}}
|
||||
render={({ field, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<Input
|
||||
type="number"
|
||||
step={trailingPercentOffsetStep}
|
||||
appendElement="%"
|
||||
data-testid={`triggerTrailingPercentOffset${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
{fieldState.error && (
|
||||
<InputError
|
||||
testId={`stop-order-error-message-trigger-trailing-percent-offset${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Controller
|
||||
name={oco ? 'ocoTriggerType' : 'triggerType'}
|
||||
control={control}
|
||||
rules={{
|
||||
deps: oco
|
||||
? ['ocoTriggerTrailingPercentOffset', 'ocoTriggerPrice']
|
||||
: ['triggerTrailingPercentOffset', 'triggerPrice'],
|
||||
}}
|
||||
render={({ field }) => {
|
||||
const { onChange, value } = field;
|
||||
return (
|
||||
<RadioGroup
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Radio
|
||||
value="price"
|
||||
id={`triggerType-price${oco ? '-oco' : ''}`}
|
||||
label={'Price'}
|
||||
/>
|
||||
<Radio
|
||||
value="trailingPercentOffset"
|
||||
id={`triggerType-trailingPercentOffset${oco ? '-oco' : ''}`}
|
||||
label={'Trailing Percent Offset'}
|
||||
/>
|
||||
</RadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const Size = ({
|
||||
control,
|
||||
sizeStep,
|
||||
oco,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
sizeStep: string;
|
||||
oco?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Controller
|
||||
name={oco ? 'ocoSize' : 'size'}
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need to provide a size'),
|
||||
min: {
|
||||
value: sizeStep,
|
||||
message: t('Size cannot be lower than ' + sizeStep),
|
||||
},
|
||||
validate: validateAmount(sizeStep, 'Size'),
|
||||
}}
|
||||
render={({ field, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
const id = `order-size${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<FormGroup labelFor={id} label={t(`Size`)} compact>
|
||||
<Input
|
||||
id={id}
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
data-testid={id}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</FormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError
|
||||
testId={`stop-order-error-message-size${oco ? '-oco' : ''}`}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Price = ({
|
||||
control,
|
||||
watch,
|
||||
priceStep,
|
||||
quoteName,
|
||||
oco,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
watch: UseFormWatch<StopOrderFormValues>;
|
||||
priceStep: string;
|
||||
quoteName: string;
|
||||
oco?: boolean;
|
||||
}) => {
|
||||
if (watch(oco ? 'ocoType' : 'type') === Schema.OrderType.TYPE_MARKET) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Controller
|
||||
name={oco ? 'ocoPrice' : 'price'}
|
||||
control={control}
|
||||
rules={{
|
||||
deps: 'type',
|
||||
required: t('You need provide a price'),
|
||||
min: {
|
||||
value: priceStep,
|
||||
message: t('Price cannot be lower than ' + priceStep),
|
||||
},
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
render={({ field, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
const id = `order-price${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
labelFor={id}
|
||||
label={t(`Price (${quoteName})`)}
|
||||
compact={true}
|
||||
>
|
||||
<Input
|
||||
id={id}
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
data-testid={id}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</FormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError
|
||||
testId={`stop-order-error-message-price${oco ? '-oco' : ''}`}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TimeInForce = ({
|
||||
control,
|
||||
oco,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
oco?: boolean;
|
||||
}) => (
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => {
|
||||
const id = `select-time-in-force${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<FormGroup label={t('Time in force')} labelFor={id} compact={true}>
|
||||
<Select
|
||||
id={id}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
hasError={!!fieldState.error}
|
||||
{...field}
|
||||
>
|
||||
<option
|
||||
key={Schema.OrderTimeInForce.TIME_IN_FORCE_IOC}
|
||||
value={Schema.OrderTimeInForce.TIME_IN_FORCE_IOC}
|
||||
>
|
||||
{timeInForceLabel(Schema.OrderTimeInForce.TIME_IN_FORCE_IOC)}
|
||||
</option>
|
||||
<option
|
||||
key={Schema.OrderTimeInForce.TIME_IN_FORCE_FOK}
|
||||
value={Schema.OrderTimeInForce.TIME_IN_FORCE_FOK}
|
||||
>
|
||||
{timeInForceLabel(Schema.OrderTimeInForce.TIME_IN_FORCE_FOK)}
|
||||
</option>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError testId={`stop-error-message-tif${oco ? '-oco' : ''}`}>
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const ReduceOnly = () => (
|
||||
<Checkbox
|
||||
name="reduce-only"
|
||||
checked={true}
|
||||
disabled={true}
|
||||
label={
|
||||
<Tooltip description={<span>{t(REDUCE_ONLY_TOOLTIP)}</span>}>
|
||||
<>{t('Reduce only')}</>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const setType = useDealTicketFormValues((state) => state.setType);
|
||||
@@ -107,6 +528,8 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
const timeInForce = watch('timeInForce');
|
||||
const rawPrice = watch('price');
|
||||
const rawSize = watch('size');
|
||||
const oco = watch('oco');
|
||||
const expiresAt = watch('expiresAt');
|
||||
|
||||
useEffect(() => {
|
||||
const size = storedFormValues?.[dealTicketType]?.size;
|
||||
@@ -155,12 +578,6 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
const trailingPercentOffsetStep = '0.1';
|
||||
|
||||
const priceFormatted =
|
||||
isPriceTrigger && triggerPrice
|
||||
? formatNumber(triggerPrice, market.decimalPlaces)
|
||||
: undefined;
|
||||
|
||||
useController({
|
||||
name: 'type',
|
||||
@@ -187,9 +604,9 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
}}
|
||||
/>
|
||||
{errors.type && (
|
||||
<TradingInputError testId="stop-order-error-message-type">
|
||||
<InputError testId="stop-order-error-message-type">
|
||||
{errors.type.message}
|
||||
</TradingInputError>
|
||||
</InputError>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
@@ -199,303 +616,128 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
<SideSelector value={field.value} onValueChange={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
<TradingFormGroup label={t('Trigger')} compact={true} labelFor="">
|
||||
<Controller
|
||||
name="triggerDirection"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const { onChange, value } = field;
|
||||
return (
|
||||
<TradingRadioGroup
|
||||
name="triggerDirection"
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
className="mb-2"
|
||||
>
|
||||
<TradingRadio
|
||||
value={
|
||||
Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
}
|
||||
id="triggerDirection-risesAbove"
|
||||
label={'Rises above'}
|
||||
/>
|
||||
<TradingRadio
|
||||
value={
|
||||
Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
}
|
||||
id="triggerDirection-fallsBelow"
|
||||
label={'Falls below'}
|
||||
/>
|
||||
</TradingRadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{isPriceTrigger && (
|
||||
<div className="mb-2">
|
||||
<Controller
|
||||
name="triggerPrice"
|
||||
rules={{
|
||||
required: t('You need provide a price'),
|
||||
min: {
|
||||
value: priceStep,
|
||||
message: t('Price cannot be lower than ' + priceStep),
|
||||
},
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<TradingInput
|
||||
data-testid="triggerPrice"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
appendElement={asset.symbol}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{errors.triggerPrice && (
|
||||
<TradingInputError testId="stop-order-error-message-trigger-price">
|
||||
{errors.triggerPrice.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isPriceTrigger && (
|
||||
<div className="mb-2">
|
||||
<Controller
|
||||
name="triggerTrailingPercentOffset"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need provide a trailing percent offset'),
|
||||
min: {
|
||||
value: trailingPercentOffsetStep,
|
||||
message: t(
|
||||
'Trailing percent offset cannot be lower than ' +
|
||||
trailingPercentOffsetStep
|
||||
),
|
||||
},
|
||||
max: {
|
||||
value: '99.9',
|
||||
message: t(
|
||||
'Trailing percent offset cannot be higher than 99.9'
|
||||
),
|
||||
},
|
||||
validate: validateAmount(
|
||||
trailingPercentOffsetStep,
|
||||
'Trailing percentage offset'
|
||||
),
|
||||
}}
|
||||
render={({ field, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<TradingInput
|
||||
type="number"
|
||||
step={trailingPercentOffsetStep}
|
||||
appendElement="%"
|
||||
data-testid="triggerTrailingPercentOffset"
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{errors.triggerTrailingPercentOffset && (
|
||||
<TradingInputError testId="stop-order-error-message-trigger-trailing-percent-offset">
|
||||
{errors.triggerTrailingPercentOffset.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Controller
|
||||
name="triggerType"
|
||||
control={control}
|
||||
rules={{ deps: ['triggerTrailingPercentOffset', 'triggerPrice'] }}
|
||||
render={({ field }) => {
|
||||
const { onChange, value } = field;
|
||||
return (
|
||||
<TradingRadioGroup
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
>
|
||||
<TradingRadio
|
||||
value="price"
|
||||
id="triggerType-price"
|
||||
label={'Price'}
|
||||
/>
|
||||
<TradingRadio
|
||||
value="trailingPercentOffset"
|
||||
id="triggerType-trailingPercentOffset"
|
||||
label={'Trailing Percent Offset'}
|
||||
/>
|
||||
</TradingRadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
<div className="mb-2">
|
||||
<div className="flex items-start gap-4">
|
||||
<TradingFormGroup
|
||||
labelFor="input-price-quote"
|
||||
label={t(`Size`)}
|
||||
className="!mb-0 flex-1"
|
||||
>
|
||||
<Controller
|
||||
name="size"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need to provide a size'),
|
||||
min: {
|
||||
value: sizeStep,
|
||||
message: t('Size cannot be lower than ' + sizeStep),
|
||||
},
|
||||
validate: validateAmount(sizeStep, 'Size'),
|
||||
}}
|
||||
render={({ field, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<TradingInput
|
||||
id="order-size"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
data-testid="order-size"
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
<div className="pt-5 leading-10">@</div>
|
||||
<div className="flex-1">
|
||||
{type === Schema.OrderType.TYPE_LIMIT ? (
|
||||
<TradingFormGroup
|
||||
labelFor="input-price-quote"
|
||||
label={t(`Price (${quoteName})`)}
|
||||
labelAlign="right"
|
||||
className="!mb-0"
|
||||
>
|
||||
<Controller
|
||||
name="price"
|
||||
control={control}
|
||||
rules={{
|
||||
deps: 'type',
|
||||
required: t('You need provide a price'),
|
||||
min: {
|
||||
value: priceStep,
|
||||
message: t('Price cannot be lower than ' + priceStep),
|
||||
},
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
render={({ field, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<TradingInput
|
||||
id="input-price-quote"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
data-testid="order-price"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
) : (
|
||||
<div
|
||||
className="text-sm text-right pt-5 leading-10"
|
||||
data-testid="price"
|
||||
>
|
||||
{priceFormatted && quoteName
|
||||
? `~${priceFormatted} ${quoteName}`
|
||||
: '-'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{errors.size && (
|
||||
<TradingInputError testId="stop-order-error-message-size">
|
||||
{errors.size.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
|
||||
{!errors.size &&
|
||||
errors.price &&
|
||||
type === Schema.OrderType.TYPE_LIMIT && (
|
||||
<TradingInputError testId="stop-order-error-message-price">
|
||||
{errors.price.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
<Trigger
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
assetSymbol={asset.symbol}
|
||||
marketPrice={marketPrice}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
/>
|
||||
<hr className="mb-4 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<Price
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
quoteName={quoteName}
|
||||
/>
|
||||
<Size control={control} sizeStep={sizeStep} />
|
||||
<TimeInForce control={control} />
|
||||
<div className="flex justify-end pb-3 gap-2">
|
||||
<ReduceOnly />
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<TradingFormGroup
|
||||
label={t('Time in force')}
|
||||
labelFor="select-time-in-force"
|
||||
compact={true}
|
||||
>
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
<hr className="mb-4 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<div className="flex justify-between pb-2 gap-2">
|
||||
<Controller
|
||||
name="oco"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const { onChange, value } = field;
|
||||
return (
|
||||
<Checkbox
|
||||
onCheckedChange={(state) => {
|
||||
onChange(state);
|
||||
setValue(
|
||||
'expiryStrategy',
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS
|
||||
);
|
||||
}}
|
||||
checked={value}
|
||||
name="oco"
|
||||
label={
|
||||
<Tooltip
|
||||
description={<span>{t('One cancels another')}</span>}
|
||||
>
|
||||
<>{t('OCO')}</>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{oco && (
|
||||
<>
|
||||
<FormGroup label={t('Type')} labelFor="">
|
||||
<Controller
|
||||
name={`ocoType`}
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const { onChange, value } = field;
|
||||
return (
|
||||
<RadioGroup
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Radio
|
||||
value={Schema.OrderType.TYPE_MARKET}
|
||||
id={`ocoTypeMarket`}
|
||||
label={'Market'}
|
||||
/>
|
||||
<Radio
|
||||
value={Schema.OrderType.TYPE_LIMIT}
|
||||
id={`ocoTypeLimit`}
|
||||
label={'Limit'}
|
||||
/>
|
||||
</RadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<Trigger
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TradingSelect
|
||||
id="select-time-in-force"
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
hasError={!!fieldState.error}
|
||||
{...field}
|
||||
>
|
||||
<option
|
||||
key={Schema.OrderTimeInForce.TIME_IN_FORCE_IOC}
|
||||
value={Schema.OrderTimeInForce.TIME_IN_FORCE_IOC}
|
||||
>
|
||||
{timeInForceLabel(Schema.OrderTimeInForce.TIME_IN_FORCE_IOC)}
|
||||
</option>
|
||||
<option
|
||||
key={Schema.OrderTimeInForce.TIME_IN_FORCE_FOK}
|
||||
value={Schema.OrderTimeInForce.TIME_IN_FORCE_FOK}
|
||||
>
|
||||
{timeInForceLabel(Schema.OrderTimeInForce.TIME_IN_FORCE_FOK)}
|
||||
</option>
|
||||
</TradingSelect>
|
||||
)}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
assetSymbol={asset.symbol}
|
||||
marketPrice={marketPrice}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
oco
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
{errors.timeInForce && (
|
||||
<TradingInputError testId="stop-error-message-tif">
|
||||
{errors.timeInForce.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 pb-2 justify-between">
|
||||
<hr className="mb-2 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<Price
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
quoteName={quoteName}
|
||||
oco
|
||||
/>
|
||||
<Size control={control} sizeStep={sizeStep} oco />
|
||||
<TimeInForce control={control} oco />
|
||||
<div className="flex justify-end mb-2 gap-2">
|
||||
<ReduceOnly />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="mb-2">
|
||||
<Controller
|
||||
name="expire"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const { onChange: onCheckedChange, value } = field;
|
||||
return (
|
||||
<TradingCheckbox
|
||||
onCheckedChange={onCheckedChange}
|
||||
<Checkbox
|
||||
onCheckedChange={(value) => {
|
||||
if (
|
||||
value &&
|
||||
(!expiresAt || new Date(expiresAt).getTime() < Date.now())
|
||||
) {
|
||||
setValue('expiresAt', formatForInput(new Date()), {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
onCheckedChange(value);
|
||||
}}
|
||||
checked={value}
|
||||
name="expire"
|
||||
label={t('Expire')}
|
||||
@@ -503,54 +745,47 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<TradingCheckbox
|
||||
name="reduce-only"
|
||||
checked={true}
|
||||
disabled={true}
|
||||
label={
|
||||
<Tooltip description={<span>{t(REDUCE_ONLY_TOOLTIP)}</span>}>
|
||||
<>{t('Reduce only')}</>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{expire && (
|
||||
<>
|
||||
<TradingFormGroup
|
||||
label={t('Strategy')}
|
||||
labelFor="expiryStrategy"
|
||||
compact={true}
|
||||
>
|
||||
<FormGroup label={t('Strategy')} labelFor="expiryStrategy">
|
||||
<Controller
|
||||
name="expiryStrategy"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const { onChange, value } = field;
|
||||
return (
|
||||
<TradingRadioGroup orientation="horizontal" {...field}>
|
||||
<TradingRadio
|
||||
<RadioGroup
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Radio
|
||||
disabled={oco}
|
||||
value={
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||
}
|
||||
id="expiryStrategy-submit"
|
||||
label={'Submit'}
|
||||
/>
|
||||
<TradingRadio
|
||||
<Radio
|
||||
value={
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS
|
||||
}
|
||||
id="expiryStrategy-cancel"
|
||||
label={'Cancel'}
|
||||
/>
|
||||
</TradingRadioGroup>
|
||||
</RadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
<div className="mb-2">
|
||||
</FormGroup>
|
||||
<div className="mb-4">
|
||||
<Controller
|
||||
name="expiresAt"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need provide a expiry time/date'),
|
||||
validate: validateExpiration,
|
||||
}}
|
||||
render={({ field }) => {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { DealTicket } from './deal-ticket';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import type { OrdersQuery } from '@vegaprotocol/orders';
|
||||
import {
|
||||
DealTicketType,
|
||||
@@ -135,20 +134,6 @@ describe('DealTicket', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should display last price for market type order', () => {
|
||||
render(generateJsx());
|
||||
act(() => {
|
||||
screen.getByTestId('order-type-Market').click();
|
||||
});
|
||||
// Assert last price is shown
|
||||
expect(screen.getByTestId('last-price')).toHaveTextContent(
|
||||
// eslint-disable-next-line
|
||||
`~${addDecimal(marketPrice, market.decimalPlaces)} ${
|
||||
market.tradableInstrument.instrument.product.quoteName
|
||||
}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should use local storage state for initial values', () => {
|
||||
const expectedOrder = {
|
||||
marketId: market.id,
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import type { FormEventHandler } from 'react';
|
||||
import { memo, useCallback, useEffect, useRef, useMemo } from 'react';
|
||||
import { Controller, useController, useForm } from 'react-hook-form';
|
||||
import { DealTicketAmount } from './deal-ticket-amount';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import {
|
||||
DealTicketFeeDetails,
|
||||
@@ -17,8 +16,10 @@ import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { mapFormValuesToOrderSubmission } from '../../utils/map-form-values-to-submission';
|
||||
import {
|
||||
TradingCheckbox,
|
||||
TradingInputError,
|
||||
TradingInput as Input,
|
||||
TradingCheckbox as Checkbox,
|
||||
TradingFormGroup as FormGroup,
|
||||
TradingInputError as InputError,
|
||||
Intent,
|
||||
Notification,
|
||||
Tooltip,
|
||||
@@ -28,11 +29,15 @@ import {
|
||||
useEstimatePositionQuery,
|
||||
useOpenVolume,
|
||||
} from '@vegaprotocol/positions';
|
||||
import { toBigNum, removeDecimal } from '@vegaprotocol/utils';
|
||||
import {
|
||||
toBigNum,
|
||||
removeDecimal,
|
||||
validateAmount,
|
||||
toDecimal,
|
||||
formatForInput,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||
import type { OrderInfo } from '@vegaprotocol/types';
|
||||
|
||||
import {
|
||||
validateExpiration,
|
||||
validateMarketState,
|
||||
@@ -52,8 +57,6 @@ import {
|
||||
useMarketAccountBalance,
|
||||
useAccountBalance,
|
||||
} from '@vegaprotocol/accounts';
|
||||
|
||||
import { OrderType } from '@vegaprotocol/types';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import {
|
||||
DealTicketType,
|
||||
@@ -64,6 +67,7 @@ import type { OrderFormValues } from '../../hooks/use-form-values';
|
||||
import { useDealTicketFormValues } from '../../hooks/use-form-values';
|
||||
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
|
||||
import noop from 'lodash/noop';
|
||||
import { isNonPersistentOrder } from '../../utils/time-in-force-persistance';
|
||||
|
||||
export const REDUCE_ONLY_TOOLTIP =
|
||||
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.';
|
||||
@@ -168,6 +172,7 @@ export const DealTicket = ({
|
||||
const rawPrice = watch('price');
|
||||
const iceberg = watch('iceberg');
|
||||
const peakSize = watch('peakSize');
|
||||
const expiresAt = watch('expiresAt');
|
||||
|
||||
useEffect(() => {
|
||||
const size = storedFormValues?.[dealTicketType]?.size;
|
||||
@@ -222,8 +227,8 @@ export const DealTicket = ({
|
||||
});
|
||||
const openVolume = useOpenVolume(pubKey, market.id) ?? '0';
|
||||
const orders = activeOrders
|
||||
? activeOrders.map<OrderInfo>((order) => ({
|
||||
isMarketOrder: order.type === OrderType.TYPE_MARKET,
|
||||
? activeOrders.map<Schema.OrderInfo>((order) => ({
|
||||
isMarketOrder: order.type === Schema.OrderType.TYPE_MARKET,
|
||||
price: order.price,
|
||||
remaining: order.remaining,
|
||||
side: order.side,
|
||||
@@ -231,7 +236,7 @@ export const DealTicket = ({
|
||||
: [];
|
||||
if (normalizedOrder) {
|
||||
orders.push({
|
||||
isMarketOrder: normalizedOrder.type === OrderType.TYPE_MARKET,
|
||||
isMarketOrder: normalizedOrder.type === Schema.OrderType.TYPE_MARKET,
|
||||
price: normalizedOrder.price ?? '0',
|
||||
remaining: normalizedOrder.size,
|
||||
side: normalizedOrder.side,
|
||||
@@ -299,12 +304,10 @@ export const DealTicket = ({
|
||||
pubKey,
|
||||
]);
|
||||
|
||||
const disablePostOnlyCheckbox = [
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
].includes(timeInForce);
|
||||
|
||||
const disableReduceOnlyCheckbox = !disablePostOnlyCheckbox;
|
||||
const nonPersistentOrder = isNonPersistentOrder(timeInForce);
|
||||
const disablePostOnlyCheckbox = nonPersistentOrder;
|
||||
const disableReduceOnlyCheckbox = !nonPersistentOrder;
|
||||
const disableIcebergCheckbox = nonPersistentOrder;
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(formValues: OrderFormValues) => {
|
||||
@@ -332,6 +335,10 @@ export const DealTicket = ({
|
||||
},
|
||||
});
|
||||
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={
|
||||
@@ -366,15 +373,82 @@ export const DealTicket = ({
|
||||
<SideSelector value={field.value} onValueChange={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
<DealTicketAmount
|
||||
type={type}
|
||||
|
||||
<Controller
|
||||
name="size"
|
||||
control={control}
|
||||
market={market}
|
||||
marketData={marketData}
|
||||
marketPrice={marketPrice || undefined}
|
||||
sizeError={errors.size?.message}
|
||||
priceError={errors.price?.message}
|
||||
rules={{
|
||||
required: t('You need to provide a size'),
|
||||
min: {
|
||||
value: sizeStep,
|
||||
message: t('Size cannot be lower than ' + sizeStep),
|
||||
},
|
||||
validate: validateAmount(sizeStep, 'Size'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
label={t('Size')}
|
||||
labelFor="input-order-size-limit"
|
||||
compact
|
||||
>
|
||||
<Input
|
||||
id="input-order-size-limit"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
data-testid="order-size"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError testId="deal-ticket-error-message-size">
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{type === Schema.OrderType.TYPE_LIMIT && (
|
||||
<Controller
|
||||
name="price"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need provide a price'),
|
||||
min: {
|
||||
value: priceStep,
|
||||
message: t('Price cannot be lower than ' + priceStep),
|
||||
},
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
labelFor="input-price-quote"
|
||||
label={t(`Price (${quoteName})`)}
|
||||
compact
|
||||
>
|
||||
<Input
|
||||
id="input-price-quote"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
data-testid="order-price"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError testId="deal-ticket-error-message-price">
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
control={control}
|
||||
@@ -388,7 +462,25 @@ export const DealTicket = ({
|
||||
<TimeInForceSelector
|
||||
value={field.value}
|
||||
orderType={type}
|
||||
onSelect={field.onChange}
|
||||
onSelect={(value) => {
|
||||
// If GTT is selected and no expiresAt time is set, or its
|
||||
// behind current time then reset the value to current time
|
||||
if (
|
||||
value === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT &&
|
||||
(!expiresAt || new Date(expiresAt).getTime() < Date.now())
|
||||
) {
|
||||
setValue('expiresAt', formatForInput(new Date()), {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
|
||||
// iceberg orders must be persistent orders, so if user
|
||||
// switches to to a non persisten tif value, remove iceberg selection
|
||||
if (iceberg && isNonPersistentOrder(value)) {
|
||||
setValue('iceberg', false);
|
||||
}
|
||||
field.onChange(value);
|
||||
}}
|
||||
market={market}
|
||||
marketData={marketData}
|
||||
errorMessage={errors.timeInForce?.message}
|
||||
@@ -401,6 +493,7 @@ export const DealTicket = ({
|
||||
name="expiresAt"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need provide a expiry time/date'),
|
||||
validate: validateExpiration,
|
||||
}}
|
||||
render={({ field }) => (
|
||||
@@ -412,12 +505,12 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-2 pb-2 justify-between">
|
||||
<div className="flex justify-between pb-2 gap-2">
|
||||
<Controller
|
||||
name="postOnly"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TradingCheckbox
|
||||
<Checkbox
|
||||
name="post-only"
|
||||
checked={!disablePostOnlyCheckbox && field.value}
|
||||
disabled={disablePostOnlyCheckbox}
|
||||
@@ -449,7 +542,7 @@ export const DealTicket = ({
|
||||
name="reduceOnly"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TradingCheckbox
|
||||
<Checkbox
|
||||
name="reduce-only"
|
||||
checked={!disableReduceOnlyCheckbox && field.value}
|
||||
disabled={disableReduceOnlyCheckbox}
|
||||
@@ -478,15 +571,16 @@ export const DealTicket = ({
|
||||
</div>
|
||||
{type === Schema.OrderType.TYPE_LIMIT && (
|
||||
<>
|
||||
<div className="flex gap-2 pb-2 justify-between">
|
||||
<div className="flex justify-between pb-2 gap-2">
|
||||
<Controller
|
||||
name="iceberg"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TradingCheckbox
|
||||
<Checkbox
|
||||
name="iceberg"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={disableIcebergCheckbox}
|
||||
label={
|
||||
<Tooltip
|
||||
description={
|
||||
@@ -572,11 +666,11 @@ export const NoWalletWarning = ({
|
||||
if (isReadOnly) {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<TradingInputError testId="deal-ticket-error-message-summary">
|
||||
<InputError testId="deal-ticket-error-message-summary">
|
||||
{
|
||||
'You need to connect your own wallet to start trading on this market'
|
||||
}
|
||||
</TradingInputError>
|
||||
</InputError>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -613,9 +707,9 @@ const SummaryMessage = memo(
|
||||
if (error?.message) {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<TradingInputError testId="deal-ticket-error-message-summary">
|
||||
<InputError testId="deal-ticket-error-message-summary">
|
||||
{error?.message}
|
||||
</TradingInputError>
|
||||
</InputError>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,30 +18,30 @@ export const ExpirySelector = ({
|
||||
onSelect,
|
||||
errorMessage,
|
||||
}: ExpirySelectorProps) => {
|
||||
const now = useRef(new Date());
|
||||
const date = value ? new Date(value) : now.current;
|
||||
const dateFormatted = formatForInput(date);
|
||||
const minDate = formatForInput(date);
|
||||
const minDateRef = useRef(new Date());
|
||||
|
||||
return (
|
||||
<TradingFormGroup
|
||||
label={t('Expiry time/date')}
|
||||
labelFor="expiration"
|
||||
compact={true}
|
||||
>
|
||||
<TradingInput
|
||||
data-testid="date-picker-field"
|
||||
id="expiration"
|
||||
type="datetime-local"
|
||||
value={dateFormatted}
|
||||
onChange={(e) => onSelect(e.target.value)}
|
||||
min={minDate}
|
||||
hasError={!!errorMessage}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<TradingInputError testId="deal-ticket-error-message-expiry">
|
||||
{errorMessage}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<div className="mb-4">
|
||||
<TradingFormGroup
|
||||
label={t('Expiry time/date')}
|
||||
labelFor="expiration"
|
||||
compact
|
||||
>
|
||||
<TradingInput
|
||||
data-testid="date-picker-field"
|
||||
id="expiration"
|
||||
type="datetime-local"
|
||||
value={value && formatForInput(new Date(value))}
|
||||
onChange={(e) => onSelect(e.target.value)}
|
||||
min={formatForInput(minDateRef.current)}
|
||||
hasError={!!errorMessage}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<TradingInputError testId="deal-ticket-error-message-expiry">
|
||||
{errorMessage}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
export * from './deal-ticket-amount';
|
||||
export * from './deal-ticket-container';
|
||||
export * from './deal-ticket-limit-amount';
|
||||
export * from './deal-ticket-market-amount';
|
||||
export * from './deal-ticket';
|
||||
export * from './deal-ticket-stop-order';
|
||||
export * from './deal-ticket-container';
|
||||
|
||||
@@ -90,32 +90,34 @@ export const TimeInForceSelector = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<TradingFormGroup
|
||||
label={t('Time in force')}
|
||||
labelFor="select-time-in-force"
|
||||
compact={true}
|
||||
>
|
||||
<TradingSelect
|
||||
id="select-time-in-force"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onSelect(e.target.value as Schema.OrderTimeInForce);
|
||||
}}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
hasError={!!errorMessage}
|
||||
<div className="mb-4">
|
||||
<TradingFormGroup
|
||||
label={t('Time in force')}
|
||||
labelFor="select-time-in-force"
|
||||
compact={true}
|
||||
>
|
||||
{options.map(([key, value]) => (
|
||||
<option key={key} value={value}>
|
||||
{timeInForceLabel(value)}
|
||||
</option>
|
||||
))}
|
||||
</TradingSelect>
|
||||
{errorMessage && (
|
||||
<TradingInputError testId="deal-ticket-error-message-tif">
|
||||
{renderError(errorMessage)}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingSelect
|
||||
id="select-time-in-force"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onSelect(e.target.value as Schema.OrderTimeInForce);
|
||||
}}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
hasError={!!errorMessage}
|
||||
>
|
||||
{options.map(([key, value]) => (
|
||||
<option key={key} value={value}>
|
||||
{timeInForceLabel(value)}
|
||||
</option>
|
||||
))}
|
||||
</TradingSelect>
|
||||
{errorMessage && (
|
||||
<TradingInputError testId="deal-ticket-error-message-tif">
|
||||
{renderError(errorMessage)}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -76,7 +76,7 @@ export const TypeToggle = ({
|
||||
<TradingDropdownTrigger
|
||||
data-testid="order-type-Stop"
|
||||
className={classNames(
|
||||
'rounded px-3 flex flex-nowrap items-center justify-center',
|
||||
'rounded px-2 flex flex-nowrap items-center justify-center',
|
||||
{
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500': selectedOption,
|
||||
}
|
||||
|
||||
@@ -28,6 +28,17 @@ export interface StopOrderFormValues {
|
||||
expire: boolean;
|
||||
expiryStrategy?: Schema.StopOrderExpiryStrategy;
|
||||
expiresAt?: string;
|
||||
|
||||
oco?: boolean;
|
||||
|
||||
ocoTriggerType: 'price' | 'trailingPercentOffset';
|
||||
ocoTriggerPrice?: string;
|
||||
ocoTriggerTrailingPercentOffset?: string;
|
||||
|
||||
ocoType: OrderType;
|
||||
ocoSize: string;
|
||||
ocoTimeInForce: OrderTimeInForce;
|
||||
ocoPrice?: string;
|
||||
}
|
||||
|
||||
export type OrderFormValues = {
|
||||
@@ -138,6 +149,7 @@ export const useDealTicketFormValues = create<Store>()(
|
||||
})),
|
||||
{
|
||||
name: 'vega_deal_ticket_store',
|
||||
version: 1,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
} from '../hooks/use-form-values';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
|
||||
import { isPersistentOrder } from './time-in-force-persistance';
|
||||
|
||||
export const mapFormValuesToOrderSubmission = (
|
||||
order: OrderFormValues,
|
||||
@@ -41,11 +42,8 @@ export const mapFormValuesToOrderSubmission = (
|
||||
? false
|
||||
: order.reduceOnly,
|
||||
icebergOpts:
|
||||
(order.type === Schema.OrderType.TYPE_MARKET ||
|
||||
[
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||
].includes(order.timeInForce)) &&
|
||||
order.type === Schema.OrderType.TYPE_LIMIT &&
|
||||
isPersistentOrder(order.timeInForce) &&
|
||||
order.iceberg &&
|
||||
order.peakSize &&
|
||||
order.minimumVisibleSize
|
||||
@@ -59,6 +57,22 @@ export const mapFormValuesToOrderSubmission = (
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const setTrigger = (
|
||||
stopOrderSetup: StopOrderSetup,
|
||||
triggerType: StopOrderFormValues['triggerPrice'],
|
||||
triggerPrice: StopOrderFormValues['triggerPrice'],
|
||||
triggerTrailingPercentOffset: StopOrderFormValues['triggerTrailingPercentOffset'],
|
||||
decimalPlaces: number
|
||||
) => {
|
||||
if (triggerType === 'price') {
|
||||
stopOrderSetup.price = removeDecimal(triggerPrice ?? '', decimalPlaces);
|
||||
} else if (triggerType === 'trailingPercentOffset') {
|
||||
stopOrderSetup.trailingPercentOffset = (
|
||||
Number(triggerTrailingPercentOffset) / 100
|
||||
).toFixed(3);
|
||||
}
|
||||
};
|
||||
|
||||
export const mapFormValuesToStopOrdersSubmission = (
|
||||
data: StopOrderFormValues,
|
||||
marketId: string,
|
||||
@@ -81,31 +95,46 @@ export const mapFormValuesToStopOrdersSubmission = (
|
||||
positionDecimalPlaces
|
||||
),
|
||||
};
|
||||
if (data.triggerType === 'price') {
|
||||
stopOrderSetup.price = removeDecimal(
|
||||
data.triggerPrice ?? '',
|
||||
setTrigger(
|
||||
stopOrderSetup,
|
||||
data.triggerType,
|
||||
data.triggerPrice,
|
||||
data.triggerTrailingPercentOffset,
|
||||
decimalPlaces
|
||||
);
|
||||
let oppositeStopOrderSetup: StopOrderSetup | undefined = undefined;
|
||||
if (data.oco) {
|
||||
oppositeStopOrderSetup = {
|
||||
orderSubmission: mapFormValuesToOrderSubmission(
|
||||
{
|
||||
type: data.ocoType,
|
||||
side: data.side,
|
||||
size: data.ocoSize,
|
||||
timeInForce: data.ocoTimeInForce,
|
||||
price: data.ocoPrice,
|
||||
reduceOnly: true,
|
||||
},
|
||||
marketId,
|
||||
decimalPlaces,
|
||||
positionDecimalPlaces
|
||||
),
|
||||
};
|
||||
setTrigger(
|
||||
oppositeStopOrderSetup,
|
||||
data.ocoTriggerType,
|
||||
data.ocoTriggerPrice,
|
||||
data.ocoTriggerTrailingPercentOffset,
|
||||
decimalPlaces
|
||||
);
|
||||
} else if (data.triggerType === 'trailingPercentOffset') {
|
||||
stopOrderSetup.trailingPercentOffset = (
|
||||
Number(data.triggerTrailingPercentOffset) / 100
|
||||
).toFixed(3);
|
||||
}
|
||||
|
||||
if (data.expire) {
|
||||
stopOrderSetup.expiresAt = data.expiresAt && toNanoSeconds(data.expiresAt);
|
||||
if (
|
||||
data.expiryStrategy ===
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS
|
||||
) {
|
||||
stopOrderSetup.expiryStrategy =
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS;
|
||||
} else if (
|
||||
data.expiryStrategy ===
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||
) {
|
||||
stopOrderSetup.expiryStrategy =
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT;
|
||||
const expiresAt = data.expiresAt && toNanoSeconds(data.expiresAt);
|
||||
stopOrderSetup.expiresAt = expiresAt;
|
||||
stopOrderSetup.expiryStrategy = data.expiryStrategy;
|
||||
if (oppositeStopOrderSetup) {
|
||||
oppositeStopOrderSetup.expiresAt = expiresAt;
|
||||
oppositeStopOrderSetup.expiryStrategy = data.expiryStrategy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,12 +143,14 @@ export const mapFormValuesToStopOrdersSubmission = (
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
) {
|
||||
submission.risesAbove = stopOrderSetup;
|
||||
submission.fallsBelow = oppositeStopOrderSetup;
|
||||
}
|
||||
if (
|
||||
data.triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
) {
|
||||
submission.fallsBelow = stopOrderSetup;
|
||||
submission.risesAbove = oppositeStopOrderSetup;
|
||||
}
|
||||
|
||||
return submission;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { mapFormValuesToOrderSubmission } from './map-form-values-to-submission';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
|
||||
import type { OrderFormValues } from '../hooks';
|
||||
|
||||
describe('mapFormValuesToOrderSubmission', () => {
|
||||
it('sets and formats price only for limit orders', () => {
|
||||
@@ -25,7 +27,7 @@ describe('mapFormValuesToOrderSubmission', () => {
|
||||
).toEqual('10000');
|
||||
});
|
||||
|
||||
it('sets and formats expiresAt only for time in force orders', () => {
|
||||
it('sets and formats expiresAt only for GTT orders', () => {
|
||||
expect(
|
||||
mapFormValuesToOrderSubmission(
|
||||
{
|
||||
@@ -49,6 +51,41 @@ describe('mapFormValuesToOrderSubmission', () => {
|
||||
).toEqual('1640995200000000000');
|
||||
});
|
||||
|
||||
it('sets and formats icebergOpts only for persisted orders', () => {
|
||||
expect(
|
||||
mapFormValuesToOrderSubmission(
|
||||
{
|
||||
type: OrderType.TYPE_LIMIT,
|
||||
timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
iceberg: true,
|
||||
peakSize: '10.00',
|
||||
minimumVisibleSize: '10.00',
|
||||
} as OrderFormValues,
|
||||
'marketId',
|
||||
2,
|
||||
2
|
||||
).icebergOpts
|
||||
).toEqual(undefined);
|
||||
|
||||
expect(
|
||||
mapFormValuesToOrderSubmission(
|
||||
{
|
||||
type: OrderType.TYPE_LIMIT,
|
||||
timeInForce: OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
iceberg: true,
|
||||
peakSize: '10.00',
|
||||
minimumVisibleSize: '10.00',
|
||||
} as OrderFormValues,
|
||||
'marketId',
|
||||
2,
|
||||
2
|
||||
).icebergOpts
|
||||
).toEqual({
|
||||
peakSize: '1000',
|
||||
minimumVisibleSize: '1000',
|
||||
});
|
||||
});
|
||||
|
||||
it('formats size', () => {
|
||||
expect(
|
||||
mapFormValuesToOrderSubmission(
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { OrderTimeInForce } from '@vegaprotocol/types';
|
||||
import {
|
||||
isNonPersistentOrder,
|
||||
isPersistentOrder,
|
||||
} from './time-in-force-persistance';
|
||||
|
||||
it('isNonPeristentOrder', () => {
|
||||
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_FOK)).toBe(true);
|
||||
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_IOC)).toBe(true);
|
||||
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GTC)).toBe(false);
|
||||
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GTT)).toBe(false);
|
||||
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GFA)).toBe(false);
|
||||
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GFN)).toBe(false);
|
||||
});
|
||||
|
||||
it('isPeristentOrder', () => {
|
||||
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_FOK)).toBe(false);
|
||||
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_IOC)).toBe(false);
|
||||
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GTC)).toBe(true);
|
||||
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GTT)).toBe(true);
|
||||
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GFA)).toBe(true);
|
||||
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GFN)).toBe(true);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { OrderTimeInForce } from '@vegaprotocol/types';
|
||||
|
||||
export const isNonPersistentOrder = (timeInForce: OrderTimeInForce) => {
|
||||
return [
|
||||
OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||
].includes(timeInForce);
|
||||
};
|
||||
|
||||
export const isPersistentOrder = (timeInForce: OrderTimeInForce) => {
|
||||
return !isNonPersistentOrder(timeInForce);
|
||||
};
|
||||
@@ -408,6 +408,12 @@ function compileFeatureFlags(): FeatureFlags {
|
||||
process.env['NX_PRODUCT_PERPETUALS']
|
||||
) as string
|
||||
),
|
||||
METAMASK_SNAPS: TRUTHY.includes(
|
||||
windowOrDefault(
|
||||
'NX_METAMASK_SNAPS',
|
||||
process.env['NX_METAMASK_SNAPS']
|
||||
) as string
|
||||
),
|
||||
};
|
||||
const EXPLORER_FLAGS = {
|
||||
EXPLORER_ASSETS: TRUTHY.includes(
|
||||
|
||||
@@ -18,7 +18,11 @@ export type Environment = z.infer<typeof envSchema>;
|
||||
export type FeatureFlags = z.infer<typeof featureFlagsSchema>;
|
||||
export type CosmicElevatorFlags = Pick<
|
||||
FeatureFlags,
|
||||
'ICEBERG_ORDERS' | 'STOP_ORDERS' | 'SUCCESSOR_MARKETS' | 'PRODUCT_PERPETUALS'
|
||||
| 'ICEBERG_ORDERS'
|
||||
| 'STOP_ORDERS'
|
||||
| 'SUCCESSOR_MARKETS'
|
||||
| 'PRODUCT_PERPETUALS'
|
||||
| 'METAMASK_SNAPS'
|
||||
>;
|
||||
export type Configuration = z.infer<typeof tomlConfigSchema>;
|
||||
export const CUSTOM_NODE_KEY = 'custom' as const;
|
||||
|
||||
@@ -77,6 +77,7 @@ const COSMIC_ELEVATOR_FLAGS = {
|
||||
STOP_ORDERS: z.optional(z.boolean()),
|
||||
ICEBERG_ORDERS: z.optional(z.boolean()),
|
||||
PRODUCT_PERPETUALS: z.optional(z.boolean()),
|
||||
METAMASK_SNAPS: z.optional(z.boolean()),
|
||||
};
|
||||
|
||||
const EXPLORER_FLAGS = {
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { isNumeric } from '@vegaprotocol/utils';
|
||||
import { PriceChangeCell } from '@vegaprotocol/datagrid';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumberPercentage,
|
||||
isNumeric,
|
||||
priceChange,
|
||||
priceChangePercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { PriceChangeCell, signedNumberCssClass } from '@vegaprotocol/datagrid';
|
||||
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useCandles } from '../../hooks/use-candles';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
marketId?: string;
|
||||
@@ -47,10 +55,39 @@ export const Last24hPriceChange = ({
|
||||
if (error || !isNumeric(decimalPlaces)) {
|
||||
return <span>-</span>;
|
||||
}
|
||||
|
||||
const candles = oneDayCandles?.map((c) => c.close) || initialValue || [];
|
||||
const change = priceChange(candles);
|
||||
const changePercentage = priceChangePercentage(candles);
|
||||
|
||||
return (
|
||||
<PriceChangeCell
|
||||
candles={oneDayCandles?.map((c) => c.close) || initialValue || []}
|
||||
decimalPlaces={decimalPlaces}
|
||||
/>
|
||||
<span
|
||||
className={classNames(
|
||||
'flex items-center gap-1',
|
||||
signedNumberCssClass(change)
|
||||
)}
|
||||
>
|
||||
<Arrow value={change} />
|
||||
<span data-testid="price-change-percentage">
|
||||
{formatNumberPercentage(new BigNumber(changePercentage.toString()), 2)}
|
||||
</span>
|
||||
<span data-testid="price-change">
|
||||
{addDecimalsFormatNumber(change.toString(), decimalPlaces ?? 0, 3)}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const Arrow = ({ value }: { value: number | bigint }) => {
|
||||
const size = 10;
|
||||
|
||||
if (value > 0) {
|
||||
return <VegaIcon name={VegaIconNames.ARROW_UP} size={size} />;
|
||||
}
|
||||
|
||||
if (value < 0) {
|
||||
return <VegaIcon name={VegaIconNames.ARROW_DOWN} size={size} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -98,7 +98,7 @@ export const Row = ({
|
||||
<div style={{ wordBreak: 'break-word' }}>
|
||||
{valueDiffersFromParentMarket ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="line-through">
|
||||
<span className="line-through dark:text-vega-dark-300">
|
||||
{getFormattedValue(parentValue)}
|
||||
</span>
|
||||
<span>{formattedValue}</span>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Fragment, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Fragment, useMemo, useState } from 'react';
|
||||
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { marketDataProvider } from '../../market-data-provider';
|
||||
import { totalFeesPercentage } from '../../market-utils';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
Lozenge,
|
||||
Splash,
|
||||
Tooltip,
|
||||
VegaIcon,
|
||||
@@ -27,9 +28,15 @@ import type {
|
||||
} from './market-info-data-provider';
|
||||
import { Last24hVolume } from '../last-24h-volume';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { DataSourceDefinition, SignerKind } from '@vegaprotocol/types';
|
||||
import { ConditionOperatorMapping } from '@vegaprotocol/types';
|
||||
import { MarketTradingModeMapping } from '@vegaprotocol/types';
|
||||
import type {
|
||||
DataSourceDefinition,
|
||||
MarketTradingMode,
|
||||
SignerKind,
|
||||
} from '@vegaprotocol/types';
|
||||
import {
|
||||
ConditionOperatorMapping,
|
||||
MarketTradingModeMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import {
|
||||
DApp,
|
||||
FLAGS,
|
||||
@@ -48,8 +55,6 @@ import {
|
||||
useSuccessorMarketQuery,
|
||||
} from '../../__generated__';
|
||||
import { useSuccessorMarketProposalDetailsQuery } from '@vegaprotocol/proposals';
|
||||
import type { MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { Signer } from '@vegaprotocol/types';
|
||||
import classNames from 'classnames';
|
||||
import compact from 'lodash/compact';
|
||||
|
||||
@@ -575,7 +580,6 @@ export const RiskFactorsInfoPanel = ({
|
||||
export const PriceMonitoringBoundsInfoPanel = ({
|
||||
market,
|
||||
triggerIndex,
|
||||
parentMarket,
|
||||
}: MarketInfoProps & {
|
||||
triggerIndex: number;
|
||||
}) => {
|
||||
@@ -584,33 +588,13 @@ export const PriceMonitoringBoundsInfoPanel = ({
|
||||
variables: { marketId: market.id },
|
||||
});
|
||||
|
||||
const { data: parentData } = useDataProvider({
|
||||
dataProvider: marketDataProvider,
|
||||
variables: { marketId: parentMarket?.id || '' },
|
||||
skip:
|
||||
!parentMarket ||
|
||||
!parentMarket?.priceMonitoringSettings?.parameters?.triggers?.[
|
||||
triggerIndex
|
||||
],
|
||||
});
|
||||
|
||||
const quoteUnit =
|
||||
market?.tradableInstrument.instrument.product?.quoteName || '';
|
||||
const parentQuoteUnit =
|
||||
parentMarket?.tradableInstrument.instrument.product?.quoteName || '';
|
||||
const isParentQuoteUnitEqual = quoteUnit === parentQuoteUnit;
|
||||
|
||||
const trigger =
|
||||
market.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
|
||||
const parentTrigger =
|
||||
parentMarket?.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
|
||||
const isParentTriggerEqual = isEqual(trigger, parentTrigger);
|
||||
|
||||
const bounds = data?.priceMonitoringBounds?.[triggerIndex];
|
||||
const parentBounds = parentData?.priceMonitoringBounds?.[triggerIndex];
|
||||
|
||||
const shouldShowParentData =
|
||||
isParentQuoteUnitEqual && isParentTriggerEqual && !!parentBounds;
|
||||
|
||||
if (!trigger) {
|
||||
console.error(
|
||||
@@ -638,14 +622,6 @@ export const PriceMonitoringBoundsInfoPanel = ({
|
||||
highestPrice: bounds.maxValidPrice,
|
||||
lowestPrice: bounds.minValidPrice,
|
||||
}}
|
||||
parentData={
|
||||
shouldShowParentData
|
||||
? {
|
||||
highestPrice: parentBounds.maxValidPrice,
|
||||
lowestPrice: parentBounds.minValidPrice,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
assetSymbol={quoteUnit}
|
||||
/>
|
||||
@@ -839,45 +815,76 @@ export const OracleInfoPanel = ({
|
||||
: (parentProduct?.dataSourceSpecForTradingTermination
|
||||
?.data as DataSourceDefinition);
|
||||
|
||||
const isParentDataSourceSpecEqual =
|
||||
parentDataSourceSpec !== undefined &&
|
||||
dataSourceSpec === parentDataSourceSpec;
|
||||
const isParentDataSourceSpecIdEqual =
|
||||
const shouldShowParentData =
|
||||
parentMarket !== undefined &&
|
||||
parentDataSourceSpecId !== undefined &&
|
||||
dataSourceSpecId === parentDataSourceSpecId;
|
||||
!isEqual(dataSourceSpec, parentDataSourceSpec);
|
||||
|
||||
const wrapperClasses = classNames('mb-4', {
|
||||
'flex items-center gap-6': shouldShowParentData,
|
||||
});
|
||||
|
||||
// We'll only provide successor parent data (if it differs) to the
|
||||
// DataSourceProof component. Having an old external link struck through
|
||||
// is unlikely to be useful.
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<DataSourceProof
|
||||
data-testid="oracle-proof-links"
|
||||
data={dataSourceSpec}
|
||||
providers={data}
|
||||
type={type}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
parentData={
|
||||
isParentDataSourceSpecEqual ? undefined : parentDataSourceSpec
|
||||
}
|
||||
parentDataSourceSpecId={
|
||||
isParentDataSourceSpecIdEqual ? undefined : parentDataSourceSpecId
|
||||
}
|
||||
/>
|
||||
<>
|
||||
{shouldShowParentData && (
|
||||
<Lozenge variant={Intent.Primary} className="text-sm">
|
||||
{t('Updated')}
|
||||
</Lozenge>
|
||||
)}
|
||||
|
||||
<ExternalLink
|
||||
data-testid="oracle-spec-links"
|
||||
href={`${VEGA_EXPLORER_URL}/oracles/${
|
||||
type === 'settlementData'
|
||||
? product.dataSourceSpecForSettlementData.id
|
||||
: product.dataSourceSpecForTradingTermination.id
|
||||
}`}
|
||||
>
|
||||
{type === 'settlementData'
|
||||
? t('View settlement data specification')
|
||||
: t('View termination specification')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<div className={wrapperClasses}>
|
||||
{shouldShowParentData &&
|
||||
parentDataSourceSpec &&
|
||||
parentDataSourceSpecId &&
|
||||
parentProduct && (
|
||||
<div className="flex flex-col gap-2 text-vega-dark-300 line-through">
|
||||
<DataSourceProof
|
||||
data-testid="oracle-proof-links"
|
||||
data={parentDataSourceSpec}
|
||||
providers={data}
|
||||
type={type}
|
||||
dataSourceSpecId={parentDataSourceSpecId}
|
||||
/>
|
||||
|
||||
<ExternalLink
|
||||
data-testid="oracle-spec-links"
|
||||
href={`${VEGA_EXPLORER_URL}/oracles/${
|
||||
type === 'settlementData'
|
||||
? parentProduct.dataSourceSpecForSettlementData.id
|
||||
: parentProduct.dataSourceSpecForTradingTermination.id
|
||||
}`}
|
||||
>
|
||||
{type === 'settlementData'
|
||||
? t('View settlement data specification')
|
||||
: t('View termination specification')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<DataSourceProof
|
||||
data-testid="oracle-proof-links"
|
||||
data={dataSourceSpec}
|
||||
providers={data}
|
||||
type={type}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
/>
|
||||
|
||||
<ExternalLink
|
||||
data-testid="oracle-spec-links"
|
||||
href={`${VEGA_EXPLORER_URL}/oracles/${
|
||||
type === 'settlementData'
|
||||
? product.dataSourceSpecForSettlementData.id
|
||||
: product.dataSourceSpecForTradingTermination.id
|
||||
}`}
|
||||
>
|
||||
{type === 'settlementData'
|
||||
? t('View settlement data specification')
|
||||
: t('View termination specification')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -886,28 +893,14 @@ export const DataSourceProof = ({
|
||||
providers,
|
||||
type,
|
||||
dataSourceSpecId,
|
||||
parentData,
|
||||
parentDataSourceSpecId,
|
||||
}: {
|
||||
data: DataSourceDefinition;
|
||||
providers: Provider[] | undefined;
|
||||
type: 'settlementData' | 'termination';
|
||||
dataSourceSpecId: string;
|
||||
parentData?: DataSourceDefinition;
|
||||
parentDataSourceSpecId?: string;
|
||||
}) => {
|
||||
// If this is a successor market, we'll only pass parent data to child
|
||||
// components for comparison if the data differs from the parent market.
|
||||
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
|
||||
const signers = data.sourceType.sourceType.signers || [];
|
||||
let parentSigners: Signer[];
|
||||
|
||||
if (
|
||||
parentData &&
|
||||
parentData.sourceType.__typename === 'DataSourceDefinitionExternal'
|
||||
) {
|
||||
parentSigners = parentData.sourceType.sourceType?.signers || [];
|
||||
}
|
||||
|
||||
if (!providers?.length) {
|
||||
return <NoOracleProof type={type} />;
|
||||
@@ -915,34 +908,15 @@ export const DataSourceProof = ({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{signers.map(({ signer }, i) => {
|
||||
const parentSigner = parentSigners?.find(
|
||||
({ signer: ParentSigner }) =>
|
||||
ParentSigner.__typename === signer.__typename
|
||||
)?.signer;
|
||||
|
||||
const isParentSignerEqual = isEqual(signer, parentSigner);
|
||||
|
||||
return isParentSignerEqual ? (
|
||||
<OracleLink
|
||||
key={i}
|
||||
providers={providers}
|
||||
signer={signer}
|
||||
type={type}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
/>
|
||||
) : (
|
||||
<OracleLink
|
||||
key={i}
|
||||
providers={providers}
|
||||
signer={signer}
|
||||
type={type}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
parentSigner={parentSigner}
|
||||
parentDataSourceSpecId={parentDataSourceSpecId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{signers.map(({ signer }, i) => (
|
||||
<OracleLink
|
||||
key={i}
|
||||
providers={providers}
|
||||
signer={signer}
|
||||
type={type}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1002,22 +976,13 @@ const OracleLink = ({
|
||||
signer,
|
||||
type,
|
||||
dataSourceSpecId,
|
||||
parentSigner,
|
||||
parentDataSourceSpecId,
|
||||
}: {
|
||||
providers: Provider[];
|
||||
signer: SignerKind;
|
||||
type: 'settlementData' | 'termination';
|
||||
dataSourceSpecId: string;
|
||||
parentSigner?: SignerKind;
|
||||
parentDataSourceSpecId?: string;
|
||||
}) => {
|
||||
// If this is a successor market, the parent market data will only have been passed
|
||||
// in if it differs from the current data.
|
||||
const signerProviders = getSignerProviders(signer, providers);
|
||||
const parentSignerProviders = parentSigner
|
||||
? getSignerProviders(parentSigner, providers)
|
||||
: [];
|
||||
|
||||
if (!signerProviders.length) {
|
||||
return <NoOracleProof type={type} />;
|
||||
@@ -1025,34 +990,13 @@ const OracleLink = ({
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
{signerProviders.map((provider) => {
|
||||
// Making the assumption here that if the provider name is the same,
|
||||
// that it is the same provider that the parent market used.
|
||||
const parentProvider = parentSignerProviders.find(
|
||||
(p) => p.name === provider.name
|
||||
);
|
||||
|
||||
const isParentProviderEqual =
|
||||
parentProvider !== undefined && isEqual(provider, parentProvider);
|
||||
|
||||
// We only want to pass the parent data to the child component if the
|
||||
// data differs from the parent market.
|
||||
return isParentProviderEqual ? (
|
||||
<OracleProfile
|
||||
key={dataSourceSpecId}
|
||||
provider={provider}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
/>
|
||||
) : (
|
||||
<OracleProfile
|
||||
key={dataSourceSpecId}
|
||||
provider={provider}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
parentProvider={parentProvider}
|
||||
parentDataSourceSpecId={parentDataSourceSpecId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{signerProviders.map((provider) => (
|
||||
<OracleProfile
|
||||
key={dataSourceSpecId}
|
||||
provider={provider}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1075,18 +1019,13 @@ const NoOracleProof = ({
|
||||
const OracleProfile = (props: {
|
||||
provider: Provider;
|
||||
dataSourceSpecId: string;
|
||||
parentProvider?: Provider;
|
||||
parentDataSourceSpecId?: string;
|
||||
}) => {
|
||||
// If this is a successor market, the parent market data will only have been passed
|
||||
// in if it differs from the current data.
|
||||
const [open, onChange] = useState(false);
|
||||
return (
|
||||
<div key={props.provider.name}>
|
||||
<OracleBasicProfile
|
||||
provider={props.provider}
|
||||
onClick={() => onChange(!open)}
|
||||
parentProvider={props.parentProvider}
|
||||
/>
|
||||
<OracleDialog {...props} open={open} onChange={onChange} />
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
ExternalLink,
|
||||
Icon,
|
||||
Intent,
|
||||
Lozenge,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
@@ -60,12 +59,10 @@ export const OracleBasicProfile = ({
|
||||
provider,
|
||||
onClick,
|
||||
markets: oracleMarkets,
|
||||
parentProvider,
|
||||
}: {
|
||||
provider: Provider;
|
||||
markets?: OracleMarketSpecFieldsFragment[] | undefined;
|
||||
onClick?: (value?: boolean) => void;
|
||||
parentProvider?: Provider;
|
||||
}) => {
|
||||
const { icon, message, intent } = getVerifiedStatusIcon(provider);
|
||||
|
||||
@@ -81,14 +78,8 @@ export const OracleBasicProfile = ({
|
||||
icon: getLinkIcon(proof.type),
|
||||
}));
|
||||
|
||||
// If this is a successor market and there's a different parent provider,
|
||||
// we'll just show that there's been a change, rather than add old data
|
||||
// in alongside the new provider.
|
||||
return (
|
||||
<>
|
||||
{parentProvider && (
|
||||
<Lozenge variant={Intent.Primary}>{t('Updated')}</Lozenge>
|
||||
)}
|
||||
<span className="flex gap-1">
|
||||
{provider.url && (
|
||||
<span className="flex align-items-bottom text-md gap-1">
|
||||
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
VegaIconNames,
|
||||
DropdownMenuItem,
|
||||
TradingDropdownCopyItem,
|
||||
Pill,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
@@ -32,7 +32,6 @@ import type {
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { StopOrder } from '../order-data-provider/stop-orders-data-provider';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import type { Order } from '../order-data-provider';
|
||||
@@ -50,236 +49,249 @@ export type StopOrdersTableProps = TypedDataAgGrid<StopOrder> & {
|
||||
isReadOnly: boolean;
|
||||
};
|
||||
|
||||
export const StopOrdersTable = memo<
|
||||
StopOrdersTableProps & { ref?: ForwardedRef<AgGridReact> }
|
||||
>(({ onCancel, onView, onMarketClick, ...props }: StopOrdersTableProps) => {
|
||||
const showAllActions = !props.isReadOnly;
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
},
|
||||
{
|
||||
headerName: t('Trigger'),
|
||||
field: 'trigger',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
sortable: false,
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'trigger'>): string =>
|
||||
data ? formatTrigger(data, data.market.decimalPlaces) : '',
|
||||
},
|
||||
{
|
||||
field: 'expiresAt',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'expiresAt'>) => {
|
||||
if (
|
||||
data &&
|
||||
value &&
|
||||
data?.expiryStrategy !==
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_UNSPECIFIED
|
||||
) {
|
||||
const expiresAt = getDateTimeFormat().format(new Date(value));
|
||||
const expiryStrategy =
|
||||
data.expiryStrategy ===
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||
? t('Submit')
|
||||
: t('Cancels');
|
||||
return `${expiryStrategy} ${expiresAt}`;
|
||||
}
|
||||
return '';
|
||||
export const StopOrdersTable = memo(
|
||||
({ onCancel, onMarketClick, onView, ...props }: StopOrdersTableProps) => {
|
||||
const showAllActions = !props.isReadOnly;
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
field: 'submission.size',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_BUY,
|
||||
[negativeClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_SELL,
|
||||
{
|
||||
headerName: t('Trigger'),
|
||||
field: 'trigger',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
sortable: false,
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'trigger'>): string =>
|
||||
data ? formatTrigger(data, data.market.decimalPlaces) : '',
|
||||
},
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) => {
|
||||
return data?.submission.size && data.market
|
||||
? toBigNum(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces ?? 0
|
||||
)
|
||||
.multipliedBy(
|
||||
data.submission.side === Schema.Side.SIDE_SELL ? -1 : 1
|
||||
{
|
||||
field: 'expiresAt',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'expiresAt'>) => {
|
||||
if (
|
||||
data &&
|
||||
value &&
|
||||
data?.expiryStrategy !==
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_UNSPECIFIED
|
||||
) {
|
||||
const expiresAt = getDateTimeFormat().format(new Date(value));
|
||||
const expiryStrategy =
|
||||
data.expiryStrategy ===
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||
? t('Submit')
|
||||
: t('Cancels');
|
||||
return `${expiryStrategy} ${expiresAt}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
field: 'submission.size',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_BUY,
|
||||
[negativeClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_SELL,
|
||||
},
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) => {
|
||||
return data?.submission.size && data.market
|
||||
? toBigNum(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces ?? 0
|
||||
)
|
||||
.toNumber()
|
||||
: undefined;
|
||||
.multipliedBy(
|
||||
data.submission.side === Schema.Side.SIDE_SELL ? -1 : 1
|
||||
)
|
||||
.toNumber()
|
||||
: undefined;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'size'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(data.submission.size)) {
|
||||
return '-';
|
||||
}
|
||||
const prefix = data
|
||||
? data.submission.side === Schema.Side.SIDE_BUY
|
||||
? '+'
|
||||
: '-'
|
||||
: '';
|
||||
return (
|
||||
prefix +
|
||||
addDecimalsFormatNumber(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'size'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(data.submission.size)) {
|
||||
return '-';
|
||||
}
|
||||
const prefix = data
|
||||
? data.submission.side === Schema.Side.SIDE_BUY
|
||||
? '+'
|
||||
: '-'
|
||||
: '';
|
||||
return (
|
||||
prefix +
|
||||
addDecimalsFormatNumber(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces
|
||||
)
|
||||
);
|
||||
{
|
||||
field: 'submission.type',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTypeMapping,
|
||||
},
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<StopOrder, 'submission.type'>) =>
|
||||
value ? Schema.OrderTypeMapping[value] : '',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'submission.type',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTypeMapping,
|
||||
{
|
||||
field: 'status',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.StopOrderStatusMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'status'>) => {
|
||||
return value ? Schema.StopOrderStatusMapping[value] : '';
|
||||
},
|
||||
cellRenderer: ({
|
||||
valueFormatted,
|
||||
data,
|
||||
}: {
|
||||
valueFormatted: string;
|
||||
data: StopOrder;
|
||||
}) => (
|
||||
<>
|
||||
<span data-testid={`order-status-${data?.id}`}>
|
||||
{valueFormatted}
|
||||
</span>
|
||||
{data.ocoLinkId && (
|
||||
<Pill
|
||||
size="xxs"
|
||||
className="uppercase ml-0.5"
|
||||
title={t('One Cancels the Other')}
|
||||
>
|
||||
OCO
|
||||
</Pill>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<StopOrder, 'submission.type'>) =>
|
||||
value ? Schema.OrderTypeMapping[value] : '',
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.StopOrderStatusMapping,
|
||||
{
|
||||
field: 'submission.price',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.price'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data?.market ||
|
||||
data.submission.type === Schema.OrderType.TYPE_MARKET ||
|
||||
!isNumeric(value)
|
||||
) {
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
},
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'status'>) => {
|
||||
return value ? Schema.StopOrderStatusMapping[value] : '';
|
||||
{
|
||||
field: 'submission.timeInForce',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTimeInForceMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.timeInForce'>) => {
|
||||
return value ? Schema.OrderTimeInForceCode[value] : '';
|
||||
},
|
||||
},
|
||||
cellRenderer: ({
|
||||
valueFormatted,
|
||||
data,
|
||||
}: {
|
||||
valueFormatted: string;
|
||||
data: StopOrder;
|
||||
}) => (
|
||||
<span data-testid={`order-status-${data?.id}`}>{valueFormatted}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'submission.price',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.price'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data?.market ||
|
||||
data.submission.type === Schema.OrderType.TYPE_MARKET ||
|
||||
!isNumeric(value)
|
||||
) {
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
{
|
||||
field: 'updatedAt',
|
||||
filter: DateRangeFilter,
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) =>
|
||||
data?.updatedAt || data?.createdAt,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<StopOrder, 'createdAt'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
const value = data.updatedAt || data.createdAt;
|
||||
return (
|
||||
<span data-value={value}>
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'submission.timeInForce',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTimeInForceMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.timeInForce'>) => {
|
||||
return value ? Schema.OrderTimeInForceCode[value] : '';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'updatedAt',
|
||||
filter: DateRangeFilter,
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) =>
|
||||
data?.updatedAt || data?.createdAt,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<StopOrder, 'createdAt'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
const value = data.updatedAt || data.createdAt;
|
||||
return (
|
||||
<span data-value={value}>
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
cellRenderer: ({ data }: { data?: StopOrder }) => {
|
||||
if (!data) return null;
|
||||
{
|
||||
colId: 'actions',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
cellRenderer: ({ data }: { data?: StopOrder }) => {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
{data.status === Schema.StopOrderStatus.STATUS_PENDING &&
|
||||
!props.isReadOnly && (
|
||||
<ButtonLink
|
||||
data-testid="cancel"
|
||||
onClick={() => onCancel(data)}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</ButtonLink>
|
||||
)}
|
||||
{data.status === Schema.StopOrderStatus.STATUS_TRIGGERED &&
|
||||
data.order && (
|
||||
<ActionsDropdown data-testid="stop-order-actions-content">
|
||||
<TradingDropdownCopyItem
|
||||
value={data.order.id}
|
||||
text={t('Copy order ID')}
|
||||
/>
|
||||
<DropdownMenuItem
|
||||
key={'view-order'}
|
||||
data-testid="view-order"
|
||||
onClick={() =>
|
||||
data.order &&
|
||||
onView({ ...data.order, market: data.market })
|
||||
}
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
{data.status === Schema.StopOrderStatus.STATUS_PENDING &&
|
||||
!props.isReadOnly && (
|
||||
<ButtonLink
|
||||
data-testid="cancel"
|
||||
onClick={() => onCancel(data)}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View order details')}
|
||||
</DropdownMenuItem>
|
||||
</ActionsDropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
{t('Cancel')}
|
||||
</ButtonLink>
|
||||
)}
|
||||
{data.status === Schema.StopOrderStatus.STATUS_TRIGGERED &&
|
||||
data.order && (
|
||||
<ActionsDropdown data-testid="stop-order-actions-content">
|
||||
<TradingDropdownCopyItem
|
||||
value={data.order.id}
|
||||
text={t('Copy order ID')}
|
||||
/>
|
||||
<DropdownMenuItem
|
||||
key={'view-order'}
|
||||
data-testid="view-order"
|
||||
onClick={() =>
|
||||
data.order &&
|
||||
onView({ ...data.order, market: data.market })
|
||||
}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View order details')}
|
||||
</DropdownMenuItem>
|
||||
</ActionsDropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
[onCancel, onMarketClick, onView, props.isReadOnly, showAllActions]
|
||||
);
|
||||
],
|
||||
[onCancel, onMarketClick, onView, props.isReadOnly, showAllActions]
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<AgGrid
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { useTimeToUpgrade } from './use-time-to-upgrade';
|
||||
import {
|
||||
ERR_NO_TIME_UNITS,
|
||||
parseDuration,
|
||||
useTimeToUpgrade,
|
||||
} from './use-time-to-upgrade';
|
||||
|
||||
jest.mock('./__generated__/BlockStatistics', () => ({
|
||||
...jest.requireActual('./__generated__/BlockStatistics'),
|
||||
@@ -8,7 +12,7 @@ jest.mock('./__generated__/BlockStatistics', () => ({
|
||||
data: {
|
||||
statistics: {
|
||||
blockHeight: 1,
|
||||
blockDuration: 500,
|
||||
blockDuration: '500ms',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -30,3 +34,25 @@ describe('useTimeToUpgrade', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseDuration', () => {
|
||||
it.each([
|
||||
['1000000ns', 1],
|
||||
['1000µs', 1],
|
||||
['1ms', 1],
|
||||
['1s', 1000],
|
||||
['1m', 60 * 1000],
|
||||
['1h', 60 * 60 * 1000],
|
||||
// below test cases are from vega
|
||||
['3.3s', 3300],
|
||||
['4m5s', 4 * 60 * 1000 + 5 * 1000],
|
||||
['4m5.001s', 4 * 60 * 1000 + 5001],
|
||||
['5h6m7.001s', 5 * 60 * 60 * 1000 + 6 * 60 * 1000 + 7001],
|
||||
['8m0.000000001s', 8 * 60 * 1000 + 1 / 1000000],
|
||||
])('parses %s to %d milliseconds', (input, output) => {
|
||||
expect(parseDuration(input)).toEqual(output);
|
||||
});
|
||||
it('throws an error when given corrupted data', () => {
|
||||
expect(() => parseDuration('blah')).toThrow(ERR_NO_TIME_UNITS);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,52 @@ const DEFAULT_POLLS = 10;
|
||||
const INTERVAL = 1000;
|
||||
const durations = [] as number[];
|
||||
|
||||
export const ERR_NO_TIME_UNITS = new Error(
|
||||
'could not parse block duration value - no time units detected'
|
||||
);
|
||||
|
||||
/**
|
||||
* Parses block duration value and output a number of milliseconds.
|
||||
* @param input The block duration input from the API, e.g. 4m5.001s
|
||||
* @returns A number of milliseconds
|
||||
*/
|
||||
export const parseDuration = (input: string) => {
|
||||
// h -> 60*60*1000
|
||||
// m -> 60*1000
|
||||
// s -> 1000
|
||||
// ms -> 1
|
||||
// µs -> 1/1000
|
||||
// ns -> 1/1000000
|
||||
let H = 0;
|
||||
let M = 0;
|
||||
let S = 0;
|
||||
const lessThanSecond = /^[0-9.]+[nµm]*s$/gu.test(input);
|
||||
const exp = /(?<hours>[0-9.]+h)?(?<minutes>[0-9.]+m)?(?<seconds>[0-9.]+s)?/gu;
|
||||
const m = exp.exec(input);
|
||||
|
||||
const hours = m?.groups?.['hours'];
|
||||
const minutes = m?.groups?.['minutes'];
|
||||
const seconds = lessThanSecond ? input : m?.groups?.['seconds'];
|
||||
if (!lessThanSecond && !hours && !minutes && !seconds) {
|
||||
throw ERR_NO_TIME_UNITS;
|
||||
}
|
||||
|
||||
if (seconds) {
|
||||
S = parseFloat(seconds);
|
||||
if (seconds.includes('ns')) S /= 1000 * 1000;
|
||||
else if (seconds.includes('µs')) S /= 1000;
|
||||
else if (seconds.includes('ms')) S *= 1;
|
||||
else if (seconds.includes('s')) S *= 1000;
|
||||
}
|
||||
if (minutes && !lessThanSecond) {
|
||||
M = parseFloat(minutes) * 60 * 1000;
|
||||
}
|
||||
if (hours && !lessThanSecond) {
|
||||
H = parseFloat(hours) * 60 * 60 * 1000;
|
||||
}
|
||||
return H + M + S;
|
||||
};
|
||||
|
||||
const useAverageBlockDuration = (polls = DEFAULT_POLLS) => {
|
||||
const [avg, setAvg] = useState<number | undefined>(undefined);
|
||||
const { data, startPolling, stopPolling, error } = useBlockStatisticsQuery({
|
||||
@@ -28,7 +74,11 @@ const useAverageBlockDuration = (polls = DEFAULT_POLLS) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (durations.length < polls && data) {
|
||||
durations.push(parseFloat(data.statistics.blockDuration));
|
||||
try {
|
||||
durations.push(parseDuration(data.statistics.blockDuration)); // ms
|
||||
} catch (err) {
|
||||
// NOOP - do not add unparsed value to AVG
|
||||
}
|
||||
}
|
||||
if (durations.length === polls) {
|
||||
const averageBlockDuration = sum(durations) / durations.length; // ms
|
||||
|
||||
@@ -177,7 +177,21 @@ module.exports = {
|
||||
success: '#00F780',
|
||||
},
|
||||
fontFamily: {
|
||||
mono: ['Roboto Mono', 'monospace'],
|
||||
mono: [
|
||||
'ui-monospace',
|
||||
'Menlo',
|
||||
'Monaco',
|
||||
'Cascadia Mono',
|
||||
'Segoe UI Mono',
|
||||
'Roboto Mono',
|
||||
'Oxygen Mono',
|
||||
'Ubuntu Monospace',
|
||||
'Source Code Pro',
|
||||
'Fira Mono',
|
||||
'Droid Sans Mono',
|
||||
'Courier New',
|
||||
'monospace',
|
||||
],
|
||||
sans: [
|
||||
'"Helvetica Neue", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
|
||||
],
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
export const IconMetaMask = ({ size = 16 }: { size: number }) => (
|
||||
<svg viewBox="0 0 47 47" fill="none" height={size}>
|
||||
<g>
|
||||
<path
|
||||
d="m40.632 6.969-14.136 10.62 2.628-6.259L40.632 6.97Z"
|
||||
fill="#E17726"
|
||||
stroke="#E17726"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m8.024 6.969 14.01 10.72-2.502-6.359L8.024 6.97ZM35.542 31.594l-3.761 5.834 8.054 2.251 2.307-7.958-6.6-.127ZM6.528 31.721 8.82 39.68l8.04-2.251-3.747-5.834-6.586.127Z"
|
||||
fill="#E27625"
|
||||
stroke="#E27625"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m16.428 21.738-2.237 3.427 7.97.368-.266-8.709-5.467 4.914ZM32.229 21.738l-5.552-5.012-.181 8.807 7.97-.368-2.237-3.427ZM16.861 37.428l4.824-2.365-4.152-3.285-.672 5.65ZM26.971 35.063l4.81 2.365-.657-5.65-4.153 3.285Z"
|
||||
fill="#E27625"
|
||||
stroke="#E27625"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m31.78 37.428-4.81-2.365.392 3.172-.042 1.345 4.46-2.152ZM16.861 37.428l4.475 2.152-.028-1.345.377-3.172-4.824 2.365Z"
|
||||
fill="#D5BFB2"
|
||||
stroke="#D5BFB2"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m21.42 29.682-4-1.19 2.825-1.316 1.174 2.506ZM27.236 29.682l1.175-2.506 2.838 1.317-4.013 1.19Z"
|
||||
fill="#233447"
|
||||
stroke="#233447"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m16.861 37.427.7-5.834-4.447.128 3.747 5.706ZM31.096 31.593l.685 5.834 3.761-5.706-4.446-.128ZM34.465 25.165l-7.97.368.741 4.15 1.175-2.507 2.838 1.317 3.216-3.328ZM17.42 28.493l2.825-1.317 1.175 2.506.74-4.149-7.97-.368 3.23 3.328Z"
|
||||
fill="#CC6228"
|
||||
stroke="#CC6228"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m14.19 25.165 3.343 6.613-.112-3.285-3.23-3.328ZM31.25 28.493l-.126 3.285 3.342-6.613-3.216 3.328ZM22.161 25.533l-.741 4.149.937 4.9.21-6.458-.406-2.591ZM26.495 25.533l-.391 2.577.196 6.471.937-4.9-.741-4.148Z"
|
||||
fill="#E27525"
|
||||
stroke="#E27525"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m27.237 29.682-.937 4.9.671.481 4.153-3.285.126-3.285-4.013 1.19ZM17.42 28.493l.112 3.285 4.153 3.285.671-.481-.937-4.9-3.999-1.19Z"
|
||||
fill="#F5841F"
|
||||
stroke="#F5841F"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m27.32 39.58.042-1.345-.363-.312h-5.342l-.35.312.029 1.345-4.475-2.152 1.566 1.303 3.175 2.223h5.439l3.188-2.224 1.552-1.302-4.46 2.152Z"
|
||||
fill="#C0AC9D"
|
||||
stroke="#C0AC9D"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m26.97 35.063-.67-.482h-3.944l-.67.482-.378 3.172.35-.312h5.34l.364.312-.391-3.172Z"
|
||||
fill="#161616"
|
||||
stroke="#161616"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m41.234 18.283 1.188-5.863-1.79-5.451-13.66 10.266 5.257 4.503 7.425 2.195 1.636-1.94-.713-.524 1.132-1.048-.867-.68 1.133-.878-.741-.58ZM6.234 12.42l1.203 5.863-.77.58 1.147.878-.867.68L8.08 21.47l-.713.524 1.636 1.94 7.425-2.195 5.257-4.503L8.025 6.97l-1.79 5.452Z"
|
||||
fill="#763E1A"
|
||||
stroke="#763E1A"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m39.654 23.933-7.425-2.195 2.237 3.427-3.342 6.613 4.419-.057h6.6l-2.49-7.788ZM16.428 21.738l-7.425 2.195-2.475 7.788h6.586l4.418.056-3.342-6.612 2.238-3.427ZM26.495 25.533l.476-8.298 2.153-5.905h-9.592l2.153 5.905.476 8.298.181 2.605.014 6.443H26.3l.014-6.443.182-2.605Z"
|
||||
fill="#F5841F"
|
||||
stroke="#F5841F"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -1,8 +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 { IconArrowUp } from './svg-icons/icon-arrow-up';
|
||||
import { IconBreakdown } from './svg-icons/icon-breakdown';
|
||||
import { IconBullet } from './svg-icons/icon-bullet';
|
||||
import { IconChevronDown } from './svg-icons/icon-chevron-down';
|
||||
@@ -20,28 +20,29 @@ import { IconGlobe } from './svg-icons/icon-globe';
|
||||
import { IconInfo } from './svg-icons/icon-info';
|
||||
import { IconKebab } from './svg-icons/icon-kebab';
|
||||
import { IconLinkedIn } from './svg-icons/icon-linkedin';
|
||||
import { IconMetaMask } from './svg-icons/icon-metamask';
|
||||
import { IconMinus } from './svg-icons/icon-minus';
|
||||
import { IconMoon } from './svg-icons/icon-moon';
|
||||
import { IconOpenExternal } from './svg-icons/icon-open-external';
|
||||
import { IconQuestionMark } from './svg-icons/icon-question-mark';
|
||||
import { IconPlus } from './svg-icons/icon-plus';
|
||||
import { IconQuestionMark } from './svg-icons/icon-question-mark';
|
||||
import { IconSearch } from './svg-icons/icon-search';
|
||||
import { IconStar } from './svg-icons/icon-star';
|
||||
import { IconTick } from './svg-icons/icon-tick';
|
||||
import { IconTicket } from './svg-icons/icon-ticket';
|
||||
import { IconTransfer } from './svg-icons/icon-transfer';
|
||||
import { IconTrendUp } from './svg-icons/icon-trend-up';
|
||||
import { IconTrendDown } from './svg-icons/icon-trend-down';
|
||||
import { IconTrendUp } from './svg-icons/icon-trend-up';
|
||||
import { IconTwitter } from './svg-icons/icon-twitter';
|
||||
import { IconVote } from './svg-icons/icon-vote';
|
||||
import { IconWithdraw } from './svg-icons/icon-withdraw';
|
||||
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',
|
||||
ARROW_UP = 'arrow-up',
|
||||
BREAKDOWN = 'breakdown',
|
||||
BULLET = 'bullet',
|
||||
CHEVRON_DOWN = 'chevron-down',
|
||||
@@ -59,18 +60,19 @@ export enum VegaIconNames {
|
||||
INFO = 'info',
|
||||
KEBAB = 'kebab',
|
||||
LINKEDIN = 'linkedin',
|
||||
METAMASK = 'metamask',
|
||||
MINUS = 'minus',
|
||||
MOON = 'moon',
|
||||
OPEN_EXTERNAL = 'open-external',
|
||||
QUESTION_MARK = 'question-mark',
|
||||
PLUS = 'plus',
|
||||
QUESTION_MARK = 'question-mark',
|
||||
SEARCH = 'search',
|
||||
STAR = 'star',
|
||||
TICK = 'tick',
|
||||
TICKET = 'ticket',
|
||||
TRANSFER = 'transfer',
|
||||
TREND_UP = 'trend-up',
|
||||
TREND_DOWN = 'trend-down',
|
||||
TREND_UP = 'trend-up',
|
||||
TWITTER = 'twitter',
|
||||
VOTE = 'vote',
|
||||
WITHDRAW = 'withdraw',
|
||||
@@ -82,38 +84,39 @@ export const VegaIconNameMap: Record<
|
||||
> = {
|
||||
'arrow-down': IconArrowDown,
|
||||
'arrow-left': IconArrowLeft,
|
||||
'arrow-up': IconArrowUp,
|
||||
'arrow-right': IconArrowRight,
|
||||
'arrow-top-right': IconArrowTopRight,
|
||||
breakdown: IconBreakdown,
|
||||
bullet: IconBullet,
|
||||
'arrow-up': IconArrowUp,
|
||||
'chevron-down': IconChevronDown,
|
||||
'chevron-left': IconChevronLeft,
|
||||
'chevron-up': IconChevronUp,
|
||||
'exclaimation-mark': IconExclaimationMark,
|
||||
'open-external': IconOpenExternal,
|
||||
'question-mark': IconQuestionMark,
|
||||
'trend-down': IconTrendDown,
|
||||
'trend-up': IconTrendUp,
|
||||
breakdown: IconBreakdown,
|
||||
bullet: IconBullet,
|
||||
cog: IconCog,
|
||||
copy: IconCopy,
|
||||
cross: IconCross,
|
||||
deposit: IconDeposit,
|
||||
edit: IconEdit,
|
||||
'exclaimation-mark': IconExclaimationMark,
|
||||
eye: IconEye,
|
||||
forum: IconForum,
|
||||
globe: IconGlobe,
|
||||
info: IconInfo,
|
||||
kebab: IconKebab,
|
||||
linkedin: IconLinkedIn,
|
||||
metamask: IconMetaMask,
|
||||
minus: IconMinus,
|
||||
moon: IconMoon,
|
||||
'open-external': IconOpenExternal,
|
||||
plus: IconPlus,
|
||||
'question-mark': IconQuestionMark,
|
||||
search: IconSearch,
|
||||
star: IconStar,
|
||||
tick: IconTick,
|
||||
ticket: IconTicket,
|
||||
transfer: IconTransfer,
|
||||
'trend-up': IconTrendUp,
|
||||
'trend-down': IconTrendDown,
|
||||
twitter: IconTwitter,
|
||||
vote: IconVote,
|
||||
withdraw: IconWithdraw,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { VegaIconNameMap } from './vega-icon-record';
|
||||
|
||||
export interface VegaIconProps {
|
||||
name: VegaIconNames;
|
||||
size?: 8 | 10 | 12 | 13 | 14 | 16 | 20 | 24 | 32;
|
||||
size?: 8 | 10 | 12 | 13 | 14 | 16 | 18 | 20 | 24 | 32;
|
||||
}
|
||||
|
||||
export const VegaIcon = ({ size = 16, name }: VegaIconProps) => {
|
||||
|
||||
@@ -34,6 +34,7 @@ export * from './progress-bar';
|
||||
export * from './radio-group';
|
||||
export * from './rounded-wrapper';
|
||||
export * from './select';
|
||||
export * from './show-more';
|
||||
export * from './simple-grid';
|
||||
export * from './slider';
|
||||
export * from './sparkline';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './show-more';
|
||||
@@ -0,0 +1,9 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { ShowMore } from './show-more';
|
||||
|
||||
describe('Button', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<ShowMore>test</ShowMore>);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { Story, Meta } from '@storybook/react';
|
||||
import { ShowMore } from './show-more';
|
||||
|
||||
export default {
|
||||
component: ShowMore,
|
||||
title: 'ShowMore',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story = (args) => (
|
||||
<ShowMore {...args}>
|
||||
<p>
|
||||
Spaceflight will never tolerate carelessness, incapacity, and neglect.
|
||||
Somewhere, somehow, we screwed up. It could have been in design, build, or
|
||||
test. Whatever it was, we should have caught it. We were too gung ho about
|
||||
the schedule and we locked out all of the problems we saw each day in our
|
||||
work. “Every element of the program was in trouble and so were we. The
|
||||
simulators were not working, Mission Control was behind in virtually every
|
||||
area, and the flight and test procedures changed daily. Nothing we did had
|
||||
any shelf life. Not one of us stood up and said, ‘Dammit, stop!’ I don’t
|
||||
know what Thompson’s committee will find as the cause, but I know what I
|
||||
find. We are the cause! We were not ready! We did not do our job. We were
|
||||
rolling the dice, hoping that things would come together by launch day,
|
||||
when in our hearts we knew it would take a miracle. We were pushing the
|
||||
schedule and betting that the Cape would slip before we did. “From this
|
||||
day forward, Flight Control will be known by two words: ‘Tough’ and
|
||||
‘Competent.’ Tough means we are forever accountable for what we do or what
|
||||
we fail to do. We will never again compromise our responsibilities. Every
|
||||
time we walk into Mission Control we will know what we stand for.
|
||||
Competent means we will never take anything for granted. We will never be
|
||||
found short in our knowledge and in our skills. Mission Control will be
|
||||
perfect. When you leave this meeting today you will go to your office and
|
||||
the first thing you will do there is to write ‘Tough and Competent’ on
|
||||
your blackboards. It will never be erased. Each day when you enter the
|
||||
room these words will remind you of the price paid by Grissom, White, and
|
||||
Chaffee. These words are the price of admission to the ranks of Mission
|
||||
Control.
|
||||
</p>
|
||||
</ShowMore>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
export const CustomMaxHeight = Template.bind({});
|
||||
CustomMaxHeight.args = {
|
||||
closedMaxHeightPx: 50,
|
||||
};
|
||||
|
||||
export const CustomOverlayColour = Template.bind({});
|
||||
CustomOverlayColour.args = {
|
||||
overlayColourOverrides: 'to-yellow-400',
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
import classNames from 'classnames';
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Button } from '../button';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
type ShowMoreProps = {
|
||||
children: ReactNode;
|
||||
closedMaxHeightPx?: number;
|
||||
overlayColourOverrides?: string;
|
||||
};
|
||||
|
||||
export const ShowMore = ({
|
||||
children,
|
||||
closedMaxHeightPx = 125,
|
||||
overlayColourOverrides,
|
||||
}: ShowMoreProps) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkHeight = () => {
|
||||
const container = containerRef.current;
|
||||
if (container) {
|
||||
container.scrollHeight < closedMaxHeightPx
|
||||
? setExpanded(true)
|
||||
: setExpanded(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkHeight();
|
||||
|
||||
window.addEventListener('resize', checkHeight);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', checkHeight);
|
||||
};
|
||||
}, [closedMaxHeightPx]);
|
||||
|
||||
const containerClasses = classNames(
|
||||
'overflow-hidden transition-all ease-in-out duration-300',
|
||||
{
|
||||
'max-h-none': expanded,
|
||||
}
|
||||
);
|
||||
|
||||
const overlayClasses = classNames(
|
||||
`absolute w-full h-16 bottom-0 left-0 transition-opacity duration-300 bg-gradient-to-b from-transparent ${
|
||||
overlayColourOverrides ? overlayColourOverrides : 'to-white dark:to-black'
|
||||
}`,
|
||||
{
|
||||
hidden: expanded,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={containerClasses}
|
||||
style={{ maxHeight: expanded ? 'none' : `${closedMaxHeightPx}px` }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div className={overlayClasses}></div>
|
||||
</div>
|
||||
|
||||
{!expanded && (
|
||||
<div className="mt-1 text-center">
|
||||
<Button size={'sm'} onClick={() => setExpanded(true)}>
|
||||
{t('Show more')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"name": "@vegaprotocol/wallet",
|
||||
"version": "0.0.1"
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
"tsConfig": "libs/wallet/tsconfig.lib.json",
|
||||
"project": "libs/wallet/package.json",
|
||||
"entryFile": "libs/wallet/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"external": ["react", "react-dom", "react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"format": ["esm", "cjs"],
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/wallet/README.md",
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ExternalLinks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
ExternalLink,
|
||||
@@ -7,12 +6,15 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { MozillaIcon } from './mozilla-icon';
|
||||
import { ChromeIcon } from './chrome-icon';
|
||||
import { useVegaWallet } from '../use-vega-wallet';
|
||||
|
||||
export const ConnectDialogTitle = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<h1
|
||||
data-testid="wallet-dialog-title"
|
||||
className="text-2xl uppercase mb-6 font-alpha calt"
|
||||
className="mb-6 text-2xl uppercase font-alpha calt"
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
@@ -24,6 +26,7 @@ export const ConnectDialogContent = ({ children }: { children: ReactNode }) => {
|
||||
};
|
||||
|
||||
export const ConnectDialogFooter = () => {
|
||||
const { links } = useVegaWallet();
|
||||
const wrapperClasses = classNames(
|
||||
'flex justify-center gap-4 mt-4',
|
||||
'px-4 md:px-8 pt-4 md:pt-6',
|
||||
@@ -32,335 +35,37 @@ export const ConnectDialogFooter = () => {
|
||||
);
|
||||
return (
|
||||
<footer className={wrapperClasses}>
|
||||
<ExternalLink
|
||||
href={ExternalLinks.VEGA_WALLET_URL_ABOUT}
|
||||
className="underline"
|
||||
>
|
||||
<ExternalLink href={links.about} className="underline">
|
||||
{t('About the Vega wallet')}{' '}
|
||||
<VegaIcon name={VegaIconNames.ARROW_TOP_RIGHT} />
|
||||
</ExternalLink>
|
||||
{ExternalLinks.VEGA_WALLET_BROWSER_LIST && (
|
||||
<>
|
||||
{' | '}
|
||||
<ExternalLink
|
||||
href={ExternalLinks.VEGA_WALLET_BROWSER_LIST}
|
||||
className="underline"
|
||||
>
|
||||
{t('Supported browsers')}{' '}
|
||||
<VegaIcon name={VegaIconNames.ARROW_TOP_RIGHT} />
|
||||
</ExternalLink>
|
||||
</>
|
||||
)}
|
||||
{' | '}
|
||||
<ExternalLink href={links.browserList} 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();
|
||||
export const BrowserIcon = ({
|
||||
chromeExtensionUrl,
|
||||
mozillaExtensionUrl,
|
||||
}: {
|
||||
chromeExtensionUrl: string;
|
||||
mozillaExtensionUrl: string;
|
||||
}) => {
|
||||
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">
|
||||
<div className="absolute top-0 flex items-center h-8 right-1">
|
||||
{!isItChrome && !isItMozilla ? (
|
||||
<>
|
||||
<a href={MOZILLA_EXTENSION_URL} target="_blank" rel="noreferrer">
|
||||
<a href={mozillaExtensionUrl} target="_blank" rel="noreferrer">
|
||||
<MozillaIcon />
|
||||
</a>{' '}
|
||||
<a href={CHROME_EXTENSION_URL} target="_blank" rel="noreferrer">
|
||||
<a href={chromeExtensionUrl} target="_blank" rel="noreferrer">
|
||||
<ChromeIcon />
|
||||
</a>
|
||||
</>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { VegaWalletConfig } from '../provider';
|
||||
import { VegaWalletProvider } from '../provider';
|
||||
import {
|
||||
VegaConnectDialog,
|
||||
CLOSE_DELAY,
|
||||
useVegaWalletDialogStore,
|
||||
} from './connect-dialog';
|
||||
import { VegaConnectDialog, CLOSE_DELAY } from './connect-dialog';
|
||||
import { useVegaWalletDialogStore } from './vega-wallet-dialog-store';
|
||||
import type { VegaConnectDialogProps } from '..';
|
||||
import {
|
||||
ClientErrors,
|
||||
@@ -15,7 +13,6 @@ import {
|
||||
ViewConnector,
|
||||
WalletError,
|
||||
} from '../connectors';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ChainIdQuery } from './__generated__/ChainId';
|
||||
import { ChainIdDocument } from './__generated__/ChainId';
|
||||
import {
|
||||
@@ -28,26 +25,14 @@ import {
|
||||
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(() => ({
|
||||
VEGA_ENV: 'TESTNET',
|
||||
VEGA_URL: 'https://vega-node.url',
|
||||
VEGA_NETWORKS: JSON.stringify({}),
|
||||
VEGA_WALLET_URL: mockVegaWalletUrl,
|
||||
GIT_BRANCH: 'test',
|
||||
GIT_COMMIT_HASH: 'abcdef',
|
||||
GIT_ORIGIN_URL: 'https://github.com/test/repo',
|
||||
HOSTED_WALLET_URL: mockHostedWalletUrl,
|
||||
}));
|
||||
|
||||
let defaultProps: VegaConnectDialogProps;
|
||||
|
||||
const INITIAL_KEY = 'some-key';
|
||||
@@ -59,7 +44,9 @@ const connectors = {
|
||||
jsonRpc,
|
||||
view,
|
||||
injected,
|
||||
snap: undefined,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
defaultProps = {
|
||||
@@ -73,12 +60,24 @@ beforeEach(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const mockVegaWalletUrl = 'http://mock.wallet.com';
|
||||
const mockHostedWalletUrl = 'http://mock.hosted.com';
|
||||
|
||||
const mockChainId = 'chain-id';
|
||||
|
||||
function generateJSX(props?: Partial<VegaConnectDialogProps>) {
|
||||
const defaultConfig: VegaWalletConfig = {
|
||||
network: 'TESTNET',
|
||||
vegaUrl: 'https://vega.xyz',
|
||||
vegaWalletServiceUrl: 'https://vegaservice.xyz',
|
||||
links: {
|
||||
explorer: 'explorer-link',
|
||||
concepts: 'concepts-link',
|
||||
chromeExtensionUrl: 'chrome-link',
|
||||
mozillaExtensionUrl: 'mozilla-link',
|
||||
},
|
||||
};
|
||||
|
||||
function generateJSX(
|
||||
props?: Partial<VegaConnectDialogProps>,
|
||||
config?: Partial<VegaWalletConfig>
|
||||
) {
|
||||
const chainIdMock: MockedResponse<ChainIdQuery> = {
|
||||
request: {
|
||||
query: ChainIdDocument,
|
||||
@@ -93,7 +92,7 @@ function generateJSX(props?: Partial<VegaConnectDialogProps>) {
|
||||
};
|
||||
return (
|
||||
<MockedProvider mocks={[chainIdMock]}>
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletProvider config={{ ...defaultConfig, ...config }}>
|
||||
<VegaConnectDialog {...defaultProps} {...props} />
|
||||
</VegaWalletProvider>
|
||||
</MockedProvider>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import classNames from 'classnames';
|
||||
import { create } from 'zustand';
|
||||
import {
|
||||
Dialog,
|
||||
Intent,
|
||||
@@ -14,15 +13,17 @@ 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 type { Connectors, VegaConnector } from '../connectors';
|
||||
import {
|
||||
DEFAULT_SNAP_ID,
|
||||
InjectedConnector,
|
||||
JsonRpcConnector,
|
||||
SnapConnector,
|
||||
ViewConnector,
|
||||
requestSnap,
|
||||
} from '../connectors';
|
||||
import { JsonRpcConnectorForm } from './json-rpc-connector-form';
|
||||
import { ViewConnectorForm } from './view-connector-form';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
BrowserIcon,
|
||||
ConnectDialogContent,
|
||||
@@ -38,33 +39,18 @@ import { useVegaWallet } from '../use-vega-wallet';
|
||||
import { InjectedConnectorForm } from './injected-connector-form';
|
||||
import { isBrowserWalletInstalled } from '../utils';
|
||||
import { useIsWalletServiceRunning } from '../use-is-wallet-service-running';
|
||||
import { useIsSnapRunning } from '../use-is-snap-running';
|
||||
import { useVegaWalletDialogStore } from './vega-wallet-dialog-store';
|
||||
|
||||
export const CLOSE_DELAY = 1700;
|
||||
type Connectors = { [key: string]: VegaConnector };
|
||||
export type WalletType = 'injected' | 'jsonRpc' | 'view';
|
||||
|
||||
export type WalletType = 'injected' | 'jsonRpc' | 'view' | 'snap';
|
||||
|
||||
export interface VegaConnectDialogProps {
|
||||
connectors: Connectors;
|
||||
riskMessage?: ReactNode;
|
||||
}
|
||||
|
||||
export interface VegaWalletDialogStore {
|
||||
vegaWalletDialogOpen: boolean;
|
||||
updateVegaWalletDialog: (open: boolean) => void;
|
||||
openVegaWalletDialog: () => void;
|
||||
closeVegaWalletDialog: () => void;
|
||||
}
|
||||
|
||||
export const useVegaWalletDialogStore = create<VegaWalletDialogStore>()(
|
||||
(set) => ({
|
||||
vegaWalletDialogOpen: false,
|
||||
updateVegaWalletDialog: (open: boolean) =>
|
||||
set({ vegaWalletDialogOpen: open }),
|
||||
openVegaWalletDialog: () => set({ vegaWalletDialogOpen: true }),
|
||||
closeVegaWalletDialog: () => set({ vegaWalletDialogOpen: false }),
|
||||
})
|
||||
);
|
||||
|
||||
export const VegaConnectDialog = ({
|
||||
connectors,
|
||||
riskMessage,
|
||||
@@ -119,12 +105,12 @@ const ConnectDialogContainer = ({
|
||||
appChainId: string;
|
||||
riskMessage?: ReactNode;
|
||||
}) => {
|
||||
const { VEGA_WALLET_URL } = useEnvironment();
|
||||
const { vegaUrl, vegaWalletServiceUrl } = useVegaWallet();
|
||||
const closeDialog = useVegaWalletDialogStore(
|
||||
(store) => store.closeVegaWalletDialog
|
||||
);
|
||||
const [selectedConnector, setSelectedConnector] = useState<VegaConnector>();
|
||||
const [walletUrl, setWalletUrl] = useState(VEGA_WALLET_URL || '');
|
||||
const [walletUrl, setWalletUrl] = useState(vegaWalletServiceUrl);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setSelectedConnector(undefined);
|
||||
@@ -143,7 +129,6 @@ const ConnectDialogContainer = ({
|
||||
|
||||
const handleSelect = (type: WalletType) => {
|
||||
const connector = connectors[type];
|
||||
connector.url = walletUrl;
|
||||
|
||||
if (!connector) {
|
||||
// we should never get here unless connectors are not configured correctly
|
||||
@@ -155,17 +140,29 @@ const ConnectDialogContainer = ({
|
||||
// Immediately connect on selection if jsonRpc is selected, we can't do this
|
||||
// for rest because we need to show an authentication form
|
||||
if (connector instanceof JsonRpcConnector) {
|
||||
connector.url = walletUrl;
|
||||
jsonRpcConnect(connector, appChainId);
|
||||
} else if (connector instanceof InjectedConnector) {
|
||||
injectedConnect(connector, appChainId);
|
||||
} else if (connector instanceof SnapConnector) {
|
||||
// Set the nodeAddress to send tx's to, normally this is handled by
|
||||
// the vega wallet
|
||||
connector.nodeAddress = new URL(vegaUrl).origin;
|
||||
injectedConnect(connector, appChainId);
|
||||
}
|
||||
};
|
||||
|
||||
const isDesktopWalletRunning = useIsWalletServiceRunning(
|
||||
walletUrl,
|
||||
connectors,
|
||||
connectors['jsonRpc'],
|
||||
appChainId
|
||||
);
|
||||
|
||||
const isSnapRunning = useIsSnapRunning(
|
||||
DEFAULT_SNAP_ID,
|
||||
Boolean(connectors['snap'])
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogContent>
|
||||
@@ -181,10 +178,12 @@ const ConnectDialogContainer = ({
|
||||
/>
|
||||
) : (
|
||||
<ConnectorList
|
||||
connectors={connectors}
|
||||
walletUrl={walletUrl}
|
||||
setWalletUrl={setWalletUrl}
|
||||
onSelect={handleSelect}
|
||||
isDesktopWalletRunning={isDesktopWalletRunning}
|
||||
isSnapRunning={isSnapRunning}
|
||||
/>
|
||||
)}
|
||||
</ConnectDialogContent>
|
||||
@@ -194,27 +193,34 @@ const ConnectDialogContainer = ({
|
||||
};
|
||||
|
||||
const ConnectorList = ({
|
||||
connectors,
|
||||
onSelect,
|
||||
walletUrl,
|
||||
setWalletUrl,
|
||||
isDesktopWalletRunning,
|
||||
isSnapRunning,
|
||||
}: {
|
||||
connectors: Connectors;
|
||||
onSelect: (type: WalletType) => void;
|
||||
walletUrl: string;
|
||||
setWalletUrl: (value: string) => void;
|
||||
isDesktopWalletRunning: boolean | null;
|
||||
isSnapRunning: boolean | null;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { pubKey, links } = useVegaWallet();
|
||||
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">
|
||||
<div className="flex items-center justify-center w-full h-full text-base gap-1">
|
||||
{t('Connect')}
|
||||
</div>
|
||||
<BrowserIcon />
|
||||
<BrowserIcon
|
||||
chromeExtensionUrl={links.chromeExtensionUrl}
|
||||
mozillaExtensionUrl={links.mozillaExtensionUrl}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -235,9 +241,51 @@ const ConnectorList = ({
|
||||
onClick={() => onSelect('injected')}
|
||||
/>
|
||||
) : (
|
||||
<GetWalletButton />
|
||||
<GetWalletButton
|
||||
chromeExtensionUrl={links.chromeExtensionUrl}
|
||||
mozillaExtensionUrl={links.mozillaExtensionUrl}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{connectors['snap'] !== undefined ? (
|
||||
<div>
|
||||
{isSnapRunning ? (
|
||||
<ConnectionOption
|
||||
type="snap"
|
||||
text={
|
||||
<>
|
||||
<div className="flex items-center justify-center w-full h-full text-base gap-1">
|
||||
{t('Connect via Vega MetaMask Snap')}
|
||||
</div>
|
||||
<div className="absolute top-0 flex items-center h-8 right-1">
|
||||
<VegaIcon name={VegaIconNames.METAMASK} size={24} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
onClick={() => {
|
||||
onSelect('snap');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ConnectionOption
|
||||
type="snap"
|
||||
text={
|
||||
<>
|
||||
<div className="flex items-center justify-center w-full h-full text-base gap-1">
|
||||
{t('Install Vega MetaMask Snap')}
|
||||
</div>
|
||||
<div className="absolute top-0 flex items-center h-8 right-1">
|
||||
<VegaIcon name={VegaIconNames.METAMASK} size={24} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
onClick={() => {
|
||||
requestSnap(DEFAULT_SNAP_ID);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<ConnectionOption
|
||||
type="view"
|
||||
@@ -282,7 +330,10 @@ const SelectedForm = ({
|
||||
onConnect: () => void;
|
||||
riskMessage?: ReactNode;
|
||||
}) => {
|
||||
if (connector instanceof InjectedConnector) {
|
||||
if (
|
||||
connector instanceof InjectedConnector ||
|
||||
connector instanceof SnapConnector
|
||||
) {
|
||||
return (
|
||||
<InjectedConnectorForm
|
||||
status={injectedState.status}
|
||||
@@ -320,30 +371,43 @@ const SelectedForm = ({
|
||||
throw new Error('No connector selected');
|
||||
};
|
||||
|
||||
export const GetWalletButton = ({ className }: { className?: string }) => {
|
||||
const { MOZILLA_EXTENSION_URL, CHROME_EXTENSION_URL } = useEnvironment();
|
||||
export const GetWalletButton = ({
|
||||
chromeExtensionUrl,
|
||||
mozillaExtensionUrl,
|
||||
className,
|
||||
}: {
|
||||
chromeExtensionUrl?: string;
|
||||
mozillaExtensionUrl?: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
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');
|
||||
window.open(mozillaExtensionUrl, '_blank');
|
||||
return;
|
||||
}
|
||||
if (isItChrome) {
|
||||
window.open(CHROME_EXTENSION_URL, '_blank');
|
||||
window.open(chromeExtensionUrl, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
const buttonContent = (
|
||||
<>
|
||||
<div className="flex items-center justify-center gap-1 text-base">
|
||||
<div className="flex items-center justify-center text-base gap-1">
|
||||
{t('Get the Vega Wallet')}
|
||||
<Pill size="xxs" intent={Intent.Info}>
|
||||
ALPHA
|
||||
</Pill>
|
||||
</div>
|
||||
<BrowserIcon />
|
||||
{chromeExtensionUrl && mozillaExtensionUrl && (
|
||||
<BrowserIcon
|
||||
chromeExtensionUrl={chromeExtensionUrl}
|
||||
mozillaExtensionUrl={mozillaExtensionUrl}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -398,7 +462,7 @@ const ConnectionOption = ({
|
||||
icon={icon}
|
||||
fill
|
||||
>
|
||||
<span className="flex justify-center items-center text-base">{text}</span>
|
||||
<span className="flex items-center justify-center text-base">{text}</span>
|
||||
</TradingButton>
|
||||
);
|
||||
};
|
||||
@@ -454,7 +518,7 @@ const CustomUrlInput = ({
|
||||
onClick={() => onSelect('jsonRpc')}
|
||||
/>
|
||||
{isDesktopWalletRunning !== null && (
|
||||
<p className="mb-6 text-sm pt-2">
|
||||
<p className="pt-2 mb-6 text-sm">
|
||||
{isDesktopWalletRunning ? (
|
||||
<button
|
||||
className="underline text-default"
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './connect-dialog';
|
||||
export * from './view-as-dialog';
|
||||
export * from './vega-wallet-dialog-store';
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { setAcknowledged } from '../storage';
|
||||
import { useVegaWallet } from '../use-vega-wallet';
|
||||
import { InjectedConnectorErrors, SnapConnectorErrors } from '../connectors';
|
||||
|
||||
export const InjectedConnectorForm = ({
|
||||
status,
|
||||
@@ -20,7 +21,6 @@ export const InjectedConnectorForm = ({
|
||||
reset,
|
||||
error,
|
||||
}: {
|
||||
// connector: JsonRpcConnector;
|
||||
appChainId: string;
|
||||
status: Status;
|
||||
error: Error | null;
|
||||
@@ -109,7 +109,7 @@ export const InjectedConnectorForm = ({
|
||||
|
||||
const Center = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<div className="flex justify-center items-center my-6">{children}</div>
|
||||
<div className="flex items-center justify-center my-6">{children}</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -131,22 +131,30 @@ const Error = ({
|
||||
);
|
||||
|
||||
if (error) {
|
||||
if (error.message === 'Invalid chain') {
|
||||
if (error.message === InjectedConnectorErrors.INVALID_CHAIN.message) {
|
||||
title = t('Wrong network');
|
||||
text = t(
|
||||
'To complete your wallet connection, set your wallet network in your app to "%s".',
|
||||
appChainId
|
||||
);
|
||||
} else if (error.message === 'window.vega not found') {
|
||||
} else if (
|
||||
error.message === InjectedConnectorErrors.VEGA_UNDEFINED.message
|
||||
) {
|
||||
title = t('No wallet detected');
|
||||
text = t('Vega browser extension not installed');
|
||||
} else if (
|
||||
error.message === SnapConnectorErrors.ETHEREUM_UNDEFINED.message ||
|
||||
error.message === SnapConnectorErrors.NODE_ADDRESS_NOT_SET.message
|
||||
) {
|
||||
title = t('Snap failed');
|
||||
text = t('Could not connect to Vega MetaMask Snap');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogTitle>{title}</ConnectDialogTitle>
|
||||
<p className="text-center mb-2 first-letter:uppercase">{text}</p>
|
||||
<p className="mb-2 text-center first-letter:uppercase">{text}</p>
|
||||
{tryAgain}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@ import type { JsonRpcConnector } from '../connectors';
|
||||
import { ClientErrors } from '../connectors';
|
||||
import { ConnectDialogTitle } from './connect-dialog-elements';
|
||||
import { Status } from '../use-json-rpc-connect';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { useVegaWallet } from '../use-vega-wallet';
|
||||
import { setAcknowledged } from '../storage';
|
||||
|
||||
@@ -156,6 +155,7 @@ const Error = ({
|
||||
appChainId: string;
|
||||
onTryAgain: () => void;
|
||||
}) => {
|
||||
const { links } = useVegaWallet();
|
||||
let title = t('Something went wrong');
|
||||
let text: ReactNode | undefined = t('An unknown error occurred');
|
||||
let tryAgain: ReactNode | null = (
|
||||
@@ -182,11 +182,9 @@ const Error = ({
|
||||
<>
|
||||
{capitalize(error.message)}
|
||||
{'. '}
|
||||
{DocsLinks && (
|
||||
<Link href={DocsLinks.VEGA_WALLET_CONCEPTS_URL}>
|
||||
{t('Read the docs to troubleshoot')}
|
||||
</Link>
|
||||
)}
|
||||
<Link href={links.concepts}>
|
||||
{t('Read the docs to troubleshoot')}
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
} else if (error.code === ServiceErrors.REQUEST_PROCESSING) {
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
export interface VegaWalletDialogStore {
|
||||
vegaWalletDialogOpen: boolean;
|
||||
updateVegaWalletDialog: (open: boolean) => void;
|
||||
openVegaWalletDialog: () => void;
|
||||
closeVegaWalletDialog: () => void;
|
||||
}
|
||||
|
||||
export const useVegaWalletDialogStore = create<VegaWalletDialogStore>()(
|
||||
(set) => ({
|
||||
vegaWalletDialogOpen: false,
|
||||
updateVegaWalletDialog: (open: boolean) =>
|
||||
set({ vegaWalletDialogOpen: open }),
|
||||
openVegaWalletDialog: () => set({ vegaWalletDialogOpen: true }),
|
||||
closeVegaWalletDialog: () => set({ vegaWalletDialogOpen: false }),
|
||||
})
|
||||
);
|
||||
@@ -1,4 +1,17 @@
|
||||
export * from './vega-connector';
|
||||
import type { InjectedConnector } from './injected-connector';
|
||||
import type { JsonRpcConnector } from './json-rpc-connector';
|
||||
import type { SnapConnector } from './snap-connector';
|
||||
import type { ViewConnector } from './view-connector';
|
||||
|
||||
export * from './injected-connector';
|
||||
export * from './json-rpc-connector';
|
||||
export * from './snap-connector';
|
||||
export * from './vega-connector';
|
||||
export * from './view-connector';
|
||||
|
||||
export type Connectors = {
|
||||
jsonRpc: JsonRpcConnector | undefined;
|
||||
injected: InjectedConnector | undefined;
|
||||
snap: SnapConnector | undefined;
|
||||
view: ViewConnector | undefined;
|
||||
};
|
||||
|
||||
@@ -41,6 +41,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export const InjectedConnectorErrors = {
|
||||
VEGA_UNDEFINED: new Error('window.vega not found'),
|
||||
INVALID_CHAIN: new Error('Invalid chain'),
|
||||
};
|
||||
|
||||
export class InjectedConnector implements VegaConnector {
|
||||
description = 'Connects using the Vega wallet browser extension';
|
||||
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
import {
|
||||
WalletError,
|
||||
type PubKey,
|
||||
type Transaction,
|
||||
type VegaConnector,
|
||||
} from './vega-connector';
|
||||
import { clearConfig, setConfig } from '../storage';
|
||||
|
||||
type RequestArguments = {
|
||||
method: string;
|
||||
params?: unknown[] | object;
|
||||
};
|
||||
type WindowEthereumProvider = {
|
||||
isMetaMask: boolean;
|
||||
request<T = unknown>(args: RequestArguments): Promise<T>;
|
||||
};
|
||||
|
||||
export const SnapConnectorErrors = {
|
||||
ETHEREUM_UNDEFINED: new Error('MetaMask extension could not be found'),
|
||||
NODE_ADDRESS_NOT_SET: new Error('nodeAddress is not set'),
|
||||
SNAP_ID_NOT_SET: new Error('snapId is not set'),
|
||||
TRANSACTION_PARSE: new Error('could not parse transaction data'),
|
||||
};
|
||||
|
||||
const ethereumRequest = <T>(args: RequestArguments): Promise<T> => {
|
||||
// can't declare `EthereumProvider` here because of the conflict with
|
||||
// type definitions of `@web3-react`
|
||||
if (
|
||||
'ethereum' in window &&
|
||||
typeof window.ethereum === 'object' &&
|
||||
window.ethereum &&
|
||||
'request' in window.ethereum &&
|
||||
'isMetaMask' in window.ethereum &&
|
||||
window.ethereum.isMetaMask &&
|
||||
typeof window.ethereum.request === 'function'
|
||||
) {
|
||||
return (window.ethereum as WindowEthereumProvider).request<T>(args);
|
||||
}
|
||||
throw SnapConnectorErrors.ETHEREUM_UNDEFINED;
|
||||
};
|
||||
|
||||
export const LOCAL_SNAP_ID = 'local:http://localhost:8080';
|
||||
export const DEFAULT_SNAP_ID = 'npm:@vegaprotocol/snap';
|
||||
|
||||
type GetSnapsResponse = Record<string, Snap>;
|
||||
|
||||
type Snap = {
|
||||
id: string;
|
||||
initialPermissions?: Record<string, unknown>;
|
||||
version: string;
|
||||
enables: boolean;
|
||||
blocked: boolean;
|
||||
};
|
||||
|
||||
type InvokeSnapRequest = {
|
||||
method: string;
|
||||
params?: object;
|
||||
};
|
||||
|
||||
type SendTransactionResponse =
|
||||
| {
|
||||
transactionHash: string;
|
||||
receivedAt: string;
|
||||
sentAt: string;
|
||||
transaction?: {
|
||||
signature?: {
|
||||
value: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
| {
|
||||
error: Error & {
|
||||
code: number;
|
||||
data: unknown;
|
||||
};
|
||||
};
|
||||
type GetChainIdResponse = {
|
||||
chainID: string;
|
||||
};
|
||||
type ListKeysResponse = { keys: PubKey[] };
|
||||
|
||||
/**
|
||||
* Requests permission for a website to communicate with the specified snaps
|
||||
* and attempts to install them if they're not already installed.
|
||||
* If the installation of any snap fails, returns the error that caused the failure.
|
||||
* More informations here: https://docs.metamask.io/snaps/reference/rpc-api/#wallet_requestsnaps
|
||||
*/
|
||||
export const requestSnap = async (
|
||||
snapId: string,
|
||||
params: Record<'version' | string, unknown> = {}
|
||||
) => {
|
||||
try {
|
||||
await ethereumRequest({
|
||||
method: 'wallet_requestSnaps',
|
||||
params: {
|
||||
[snapId]: params,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
// NOOP - rejected by user
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the list of all installed snaps.
|
||||
* More information here: https://docs.metamask.io/snaps/reference/rpc-api/#wallet_getsnaps
|
||||
*/
|
||||
export const getSnaps = async (): Promise<GetSnapsResponse> => {
|
||||
return (await ethereumRequest({
|
||||
method: 'wallet_getSnaps',
|
||||
})) as GetSnapsResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the requested snap by `snapId` and an optional `version`
|
||||
*/
|
||||
export const getSnap = async (
|
||||
snapId: string,
|
||||
version?: string
|
||||
): Promise<Snap | undefined> => {
|
||||
try {
|
||||
const snaps = await getSnaps();
|
||||
return Object.values(snaps).find(
|
||||
(snap) => snap.id === snapId && (!version || snap.version === version)
|
||||
);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const invokeSnap = async <T>(
|
||||
snapId: string,
|
||||
request: InvokeSnapRequest
|
||||
) => {
|
||||
const req = {
|
||||
method: 'wallet_invokeSnap',
|
||||
params: {
|
||||
snapId,
|
||||
request,
|
||||
},
|
||||
};
|
||||
return await ethereumRequest<T>(req);
|
||||
};
|
||||
|
||||
export class SnapConnector implements VegaConnector {
|
||||
description = "Connects using Vega Protocol's MetaMask snap";
|
||||
snapId: string | undefined = undefined;
|
||||
nodeAddress: string | undefined = undefined;
|
||||
|
||||
// note we cannot set nodeAddress in the constructor because the
|
||||
// trading app will not know what the vega url is until the app runs
|
||||
constructor(snapId = DEFAULT_SNAP_ID) {
|
||||
this.snapId = snapId;
|
||||
}
|
||||
|
||||
async listKeys() {
|
||||
if (!this.snapId) throw SnapConnectorErrors.SNAP_ID_NOT_SET;
|
||||
return await invokeSnap<ListKeysResponse>(this.snapId, {
|
||||
method: 'client.list_keys',
|
||||
});
|
||||
}
|
||||
|
||||
async connect() {
|
||||
const res = await this.listKeys();
|
||||
setConfig({
|
||||
connector: 'snap',
|
||||
token: null, // no token required for snap
|
||||
url: null, // no url required for snap
|
||||
});
|
||||
return res?.keys;
|
||||
}
|
||||
|
||||
async sendTx(pubKey: string, transaction: Transaction) {
|
||||
if (!this.nodeAddress) throw SnapConnectorErrors.NODE_ADDRESS_NOT_SET;
|
||||
if (!this.snapId) throw SnapConnectorErrors.SNAP_ID_NOT_SET;
|
||||
|
||||
// This step is needed to strip the transaction object from any additional
|
||||
// properties, such as `__proto__`, etc.
|
||||
let txData = null;
|
||||
try {
|
||||
txData = JSON.parse(JSON.stringify(transaction));
|
||||
} catch (err) {
|
||||
throw SnapConnectorErrors.TRANSACTION_PARSE;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
method: 'client.send_transaction',
|
||||
params: {
|
||||
sendingMode: 'TYPE_SYNC',
|
||||
transaction: txData,
|
||||
publicKey: pubKey,
|
||||
networkEndpoints: [this.nodeAddress],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await invokeSnap<SendTransactionResponse>(
|
||||
this.snapId,
|
||||
payload
|
||||
);
|
||||
|
||||
if ('error' in result) {
|
||||
const { message, code, data } = result.error;
|
||||
throw new WalletError(
|
||||
message,
|
||||
code,
|
||||
typeof data === 'string' ? data : ''
|
||||
);
|
||||
}
|
||||
|
||||
if (!result?.transaction?.signature) {
|
||||
throw new Error('could not retrieve transaction siganture');
|
||||
}
|
||||
|
||||
return {
|
||||
transactionHash: result.transactionHash,
|
||||
signature: result?.transaction?.signature?.value,
|
||||
receivedAt: result.receivedAt,
|
||||
sentAt: result.sentAt,
|
||||
};
|
||||
}
|
||||
|
||||
async getChainId(): Promise<GetChainIdResponse> {
|
||||
if (!this.nodeAddress) throw SnapConnectorErrors.NODE_ADDRESS_NOT_SET;
|
||||
if (!this.snapId) throw SnapConnectorErrors.SNAP_ID_NOT_SET;
|
||||
|
||||
const response = await invokeSnap<GetChainIdResponse>(this.snapId, {
|
||||
method: 'client.get_chain_id',
|
||||
params: { networkEndpoints: [this.nodeAddress] },
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
clearConfig();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,15 @@ import type {
|
||||
} from './connectors';
|
||||
|
||||
export interface VegaWalletContextShape {
|
||||
/** Current connected network */
|
||||
network: string;
|
||||
|
||||
/** Url of current connected node */
|
||||
vegaUrl: string;
|
||||
|
||||
/** Url of running wallet service */
|
||||
vegaWalletServiceUrl: string;
|
||||
|
||||
/** If the current connector does not support signing transactions */
|
||||
isReadOnly: boolean;
|
||||
/** The current select public key */
|
||||
@@ -37,6 +46,16 @@ export interface VegaWalletContextShape {
|
||||
|
||||
/** Acknowledge disclaimer */
|
||||
acknowledgeNeeded?: boolean;
|
||||
|
||||
/** Useful links for wallet users */
|
||||
links: {
|
||||
explorer: string;
|
||||
about: string;
|
||||
concepts: string;
|
||||
browserList: string;
|
||||
chromeExtensionUrl: string;
|
||||
mozillaExtensionUrl: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const VegaWalletContext = createContext<
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
export * from './context';
|
||||
export * from './use-vega-wallet';
|
||||
export * from './connectors';
|
||||
export * from './context';
|
||||
export * from './use-vega-transaction';
|
||||
export * from './use-vega-wallet';
|
||||
export * from './use-vega-transaction-manager';
|
||||
export * from './use-vega-transaction-store';
|
||||
export * from './use-vega-transaction-updater';
|
||||
export * from './use-transaction-result';
|
||||
export * from './use-eager-connect';
|
||||
export * from './manage-dialog';
|
||||
export * from './vega-transaction-dialog';
|
||||
export * from './provider';
|
||||
export * from './connect-dialog';
|
||||
export * from './utils';
|
||||
export * from './storage';
|
||||
export * from './types';
|
||||
export * from './vega-transaction-dialog';
|
||||
export * from './__generated__/TransactionResult';
|
||||
export * from './__generated__/WithdrawalApproval';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
Intent,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '..';
|
||||
import { useVegaWallet } from '../use-vega-wallet';
|
||||
|
||||
export interface VegaManageDialogProps {
|
||||
dialogOpen: boolean;
|
||||
@@ -38,13 +38,13 @@ export const VegaManageDialog = ({
|
||||
className="mb-2 last:mb-0"
|
||||
>
|
||||
<div
|
||||
className="flex gap-4 justify-between text-sm"
|
||||
className="flex justify-between text-sm gap-4"
|
||||
data-testid={isSelected ? 'selected-key' : ''}
|
||||
>
|
||||
<p data-testid="vega-public-key-full">
|
||||
{truncateByChars(pk.publicKey)}
|
||||
</p>
|
||||
<div className="flex gap-4 ml-auto">
|
||||
<div className="flex ml-auto gap-4">
|
||||
{!isSelected && (
|
||||
<button
|
||||
onClick={() => {
|
||||
|
||||
@@ -2,19 +2,32 @@ import { act, renderHook } from '@testing-library/react';
|
||||
import type { Transaction } from './connectors';
|
||||
import { ViewConnector, JsonRpcConnector } from './connectors';
|
||||
import { useVegaWallet } from './use-vega-wallet';
|
||||
import type { VegaWalletConfig } from './provider';
|
||||
import { VegaWalletProvider } from './provider';
|
||||
import { LocalStorage } from '@vegaprotocol/utils';
|
||||
import type { ReactNode } from 'react';
|
||||
import { WALLET_KEY } from './storage';
|
||||
import * as Environment from '@vegaprotocol/environment';
|
||||
import * as ReactHelpers from '@vegaprotocol/react-helpers';
|
||||
|
||||
const jsonRpcConnector = new JsonRpcConnector();
|
||||
const viewConnector = new ViewConnector();
|
||||
|
||||
const setup = () => {
|
||||
const defaultConfig: VegaWalletConfig = {
|
||||
network: 'TESTNET',
|
||||
vegaUrl: 'https://vega.xyz',
|
||||
vegaWalletServiceUrl: 'https://vegaservice.xyz',
|
||||
links: {
|
||||
explorer: 'explorer-link',
|
||||
concepts: 'concepts-link',
|
||||
chromeExtensionUrl: 'chrome-link',
|
||||
mozillaExtensionUrl: 'mozilla-link',
|
||||
},
|
||||
};
|
||||
|
||||
const setup = (config?: Partial<VegaWalletConfig>) => {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<VegaWalletProvider>{children}</VegaWalletProvider>
|
||||
<VegaWalletProvider config={{ ...defaultConfig, ...config }}>
|
||||
{children}
|
||||
</VegaWalletProvider>
|
||||
);
|
||||
return renderHook(() => useVegaWallet(), { wrapper });
|
||||
};
|
||||
@@ -51,6 +64,9 @@ describe('VegaWalletProvider', () => {
|
||||
|
||||
// Default state
|
||||
expect(result.current).toEqual({
|
||||
network: defaultConfig.network,
|
||||
vegaUrl: defaultConfig.vegaUrl,
|
||||
vegaWalletServiceUrl: defaultConfig.vegaWalletServiceUrl,
|
||||
acknowledgeNeeded: false,
|
||||
pubKey: null,
|
||||
pubKeys: null,
|
||||
@@ -60,6 +76,11 @@ describe('VegaWalletProvider', () => {
|
||||
disconnect: expect.any(Function),
|
||||
sendTx: expect.any(Function),
|
||||
fetchPubKeys: expect.any(Function || undefined),
|
||||
links: {
|
||||
about: expect.any(String),
|
||||
browserList: expect.any(String),
|
||||
...defaultConfig.links,
|
||||
},
|
||||
});
|
||||
|
||||
// Connect
|
||||
@@ -91,17 +112,7 @@ describe('VegaWalletProvider', () => {
|
||||
const { result } = setup();
|
||||
|
||||
// Default state
|
||||
expect(result.current).toEqual({
|
||||
acknowledgeNeeded: false,
|
||||
pubKey: null,
|
||||
pubKeys: null,
|
||||
isReadOnly: false,
|
||||
selectPubKey: expect.any(Function),
|
||||
connect: expect.any(Function),
|
||||
disconnect: expect.any(Function),
|
||||
sendTx: expect.any(Function),
|
||||
fetchPubKeys: expect.any(Function),
|
||||
});
|
||||
expect(result.current.pubKey).toEqual(null);
|
||||
|
||||
// Connect
|
||||
await act(async () => {
|
||||
@@ -128,6 +139,7 @@ describe('VegaWalletProvider', () => {
|
||||
result.current.connect(jsonRpcConnector);
|
||||
result.current.selectPubKey(mockPubKeys[0].publicKey);
|
||||
});
|
||||
|
||||
expect(result.current.pubKey).toBe(mockPubKeys[0].publicKey);
|
||||
|
||||
// Disconnect
|
||||
@@ -154,22 +166,12 @@ describe('VegaWalletProvider', () => {
|
||||
});
|
||||
|
||||
it('acknowledgeNeeded will set on', async () => {
|
||||
jest
|
||||
.spyOn(Environment, 'useEnvironment')
|
||||
.mockReturnValue({ VEGA_ENV: 'MAINNET' });
|
||||
jest.spyOn(ReactHelpers, 'useLocalStorage').mockImplementation(() => [
|
||||
'',
|
||||
() => {
|
||||
/**/
|
||||
},
|
||||
() => {
|
||||
/**/
|
||||
},
|
||||
]);
|
||||
jest
|
||||
.spyOn(viewConnector, 'connect')
|
||||
.mockImplementation(() => Promise.resolve(mockPubKeys));
|
||||
const { result } = setup();
|
||||
|
||||
const { result } = setup({ network: 'MAINNET' });
|
||||
|
||||
expect(result.current.acknowledgeNeeded).toBe(true);
|
||||
|
||||
await act(async () => {
|
||||
|
||||
@@ -11,14 +11,45 @@ import type {
|
||||
import { VegaWalletContext } from './context';
|
||||
import { WALLET_KEY, WALLET_RISK_ACCEPTED_KEY } from './storage';
|
||||
import { ViewConnector } from './connectors';
|
||||
import { useEnvironment, Networks } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
|
||||
type Networks =
|
||||
| 'MAINNET'
|
||||
| 'MAINNET_MIRROR'
|
||||
| 'TESTNET'
|
||||
| 'VALIDATOR_TESTNET'
|
||||
| 'STAGNET1'
|
||||
| 'DEVNET'
|
||||
| 'CUSTOM';
|
||||
|
||||
interface VegaWalletLinks {
|
||||
explorer: string;
|
||||
concepts: string;
|
||||
chromeExtensionUrl: string;
|
||||
mozillaExtensionUrl: string;
|
||||
}
|
||||
|
||||
export interface VegaWalletConfig {
|
||||
network: Networks;
|
||||
vegaUrl: string;
|
||||
vegaWalletServiceUrl: string;
|
||||
links: VegaWalletLinks;
|
||||
}
|
||||
|
||||
const ExternalLinks = {
|
||||
VEGA_WALLET_URL_ABOUT: 'https://vega.xyz/wallet/#overview',
|
||||
VEGA_WALLET_BROWSER_LIST: '',
|
||||
};
|
||||
|
||||
interface VegaWalletProviderProps {
|
||||
children: ReactNode;
|
||||
config: VegaWalletConfig;
|
||||
}
|
||||
|
||||
export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
|
||||
export const VegaWalletProvider = ({
|
||||
children,
|
||||
config,
|
||||
}: VegaWalletProviderProps) => {
|
||||
// Current selected pubKey
|
||||
const [pubKey, setPubKey] = useState<string | null>(null);
|
||||
const [isReadOnly, setIsReadOnly] = useState<boolean>(false);
|
||||
@@ -109,13 +140,23 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
|
||||
return connector.current.sendTx(pubkey, transaction);
|
||||
}, []);
|
||||
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const [riskAcceptedValue] = useLocalStorage(WALLET_RISK_ACCEPTED_KEY);
|
||||
const acknowledgeNeeded =
|
||||
VEGA_ENV === Networks.MAINNET && riskAcceptedValue !== 'true';
|
||||
config.network === 'MAINNET' && riskAcceptedValue !== 'true';
|
||||
|
||||
const contextValue = useMemo<VegaWalletContextShape>(() => {
|
||||
return {
|
||||
vegaUrl: config.vegaUrl,
|
||||
vegaWalletServiceUrl: config.vegaWalletServiceUrl,
|
||||
network: config.network,
|
||||
links: {
|
||||
explorer: config.links.explorer,
|
||||
about: ExternalLinks.VEGA_WALLET_URL_ABOUT,
|
||||
browserList: ExternalLinks.VEGA_WALLET_BROWSER_LIST,
|
||||
concepts: config.links.concepts,
|
||||
chromeExtensionUrl: config.links.chromeExtensionUrl,
|
||||
mozillaExtensionUrl: config.links.mozillaExtensionUrl,
|
||||
},
|
||||
isReadOnly,
|
||||
pubKey,
|
||||
pubKeys,
|
||||
@@ -127,6 +168,7 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
|
||||
acknowledgeNeeded,
|
||||
};
|
||||
}, [
|
||||
config,
|
||||
isReadOnly,
|
||||
pubKey,
|
||||
pubKeys,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { LocalStorage } from '@vegaprotocol/utils';
|
||||
|
||||
interface ConnectorConfig {
|
||||
token: string | null;
|
||||
connector: 'injected' | 'jsonRpc' | 'view';
|
||||
connector: 'injected' | 'jsonRpc' | 'view' | 'snap';
|
||||
url: string | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export enum VegaTxStatus {
|
||||
Default = 'Default',
|
||||
Requested = 'Requested',
|
||||
Pending = 'Pending',
|
||||
Error = 'Error',
|
||||
Complete = 'Complete',
|
||||
}
|
||||
|
||||
export interface VegaTxState {
|
||||
status: VegaTxStatus;
|
||||
error: Error | null;
|
||||
txHash: string | null;
|
||||
signature: string | null;
|
||||
dialogOpen: boolean;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useVegaWallet } from './';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { VegaConnector } from './connectors/vega-connector';
|
||||
import { InjectedConnector } from './connectors/injected-connector';
|
||||
import { SnapConnector } from './connectors/snap-connector';
|
||||
import { getConfig } from './storage';
|
||||
import type { Connectors } from './connectors';
|
||||
import { useVegaWallet } from './use-vega-wallet';
|
||||
|
||||
export function useEagerConnect(Connectors: {
|
||||
[connector: string]: VegaConnector;
|
||||
}) {
|
||||
export function useEagerConnect(connectors: Connectors) {
|
||||
const [connecting, setConnecting] = useState(true);
|
||||
const { connect, acknowledgeNeeded } = useVegaWallet();
|
||||
const { vegaUrl, connect, acknowledgeNeeded } = useVegaWallet();
|
||||
|
||||
useEffect(() => {
|
||||
const attemptConnect = async () => {
|
||||
@@ -20,7 +20,7 @@ export function useEagerConnect(Connectors: {
|
||||
|
||||
// Use the connector string in local storage to find the right connector to auto
|
||||
// connect to
|
||||
const connector = Connectors[cfg.connector];
|
||||
const connector = connectors[cfg.connector];
|
||||
|
||||
// Developer hasn't provided this connector
|
||||
if (!connector) {
|
||||
@@ -30,14 +30,16 @@ export function useEagerConnect(Connectors: {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (cfg.connector === 'injected') {
|
||||
const injectedInstance = Connectors[cfg.connector];
|
||||
// @ts-ignore only injected wallet has connectWallet method
|
||||
await injectedInstance.connectWallet();
|
||||
await connect(injectedInstance);
|
||||
if (connector instanceof InjectedConnector) {
|
||||
await connector.connectWallet();
|
||||
await connect(connector);
|
||||
} else if (connector instanceof SnapConnector) {
|
||||
connector.nodeAddress = new URL(vegaUrl).origin;
|
||||
await connect(connector);
|
||||
} else {
|
||||
await connect(Connectors[cfg.connector]);
|
||||
await connect(connector);
|
||||
}
|
||||
} catch {
|
||||
console.warn(`Failed to connect with connector: ${cfg.connector}`);
|
||||
@@ -49,7 +51,7 @@ export function useEagerConnect(Connectors: {
|
||||
if (typeof window !== 'undefined') {
|
||||
attemptConnect();
|
||||
}
|
||||
}, [connect, Connectors, acknowledgeNeeded]);
|
||||
}, [connect, connectors, acknowledgeNeeded, vegaUrl]);
|
||||
|
||||
return connecting;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { Status, useInjectedConnector } from './use-injected-connector';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { VegaWalletConfig } from './provider';
|
||||
import { VegaWalletProvider } from './provider';
|
||||
import { InjectedConnector } from './connectors';
|
||||
import { mockBrowserWallet } from './test-helpers';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Networks } from '@vegaprotocol/environment';
|
||||
|
||||
jest.mock('@vegaprotocol/environment');
|
||||
const defaultConfig: VegaWalletConfig = {
|
||||
network: 'TESTNET',
|
||||
vegaUrl: 'https://vega.xyz',
|
||||
vegaWalletServiceUrl: 'https://vegaservice.xyz',
|
||||
links: {
|
||||
explorer: 'explorer-link',
|
||||
concepts: 'concepts-link',
|
||||
chromeExtensionUrl: 'chrome-link',
|
||||
mozillaExtensionUrl: 'mozilla-link',
|
||||
},
|
||||
};
|
||||
|
||||
const setup = (callback = jest.fn()) => {
|
||||
const setup = (callback = jest.fn(), config?: Partial<VegaWalletConfig>) => {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<VegaWalletProvider>{children}</VegaWalletProvider>
|
||||
<VegaWalletProvider config={{ ...defaultConfig, ...config }}>
|
||||
{children}
|
||||
</VegaWalletProvider>
|
||||
);
|
||||
return renderHook(() => useInjectedConnector(callback), { wrapper });
|
||||
};
|
||||
@@ -19,10 +30,6 @@ const setup = (callback = jest.fn()) => {
|
||||
const injected = new InjectedConnector();
|
||||
|
||||
describe('useInjectedConnector', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore useEnvironment has been mocked
|
||||
useEnvironment.mockImplementation(() => ({ VEGA_ENV: Networks.TESTNET }));
|
||||
});
|
||||
it('attempts connection', async () => {
|
||||
const { result } = setup();
|
||||
expect(typeof result.current.connect).toBe('function');
|
||||
@@ -84,11 +91,8 @@ describe('useInjectedConnector', () => {
|
||||
|
||||
it('connects when aknowledgement required', async () => {
|
||||
const callback = jest.fn();
|
||||
// @ts-ignore useEnvironment has been mocked
|
||||
useEnvironment.mockImplementation(() => ({ VEGA_ENV: Networks.MAINNET }));
|
||||
|
||||
const vega = mockBrowserWallet();
|
||||
const { result } = setup(callback);
|
||||
const { result } = setup(callback, { network: 'MAINNET' });
|
||||
|
||||
act(() => {
|
||||
result.current.connect(injected, '1'); // default mock chainId is '1'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user