Merge branch 'master' of github.com:vegaprotocol/frontend-monorepo
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
name: Capsule tests -- nightly
|
||||
name: Capsule tests -- night run
|
||||
|
||||
# This workflow runs the frontend tests against latest develop of the core to preempt breaking changes
|
||||
|
||||
@@ -12,6 +12,7 @@ NX_EXPLORER_ASSETS=1
|
||||
NX_EXPLORER_GENESIS=1
|
||||
NX_EXPLORER_GOVERNANCE=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=0
|
||||
NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
|
||||
@@ -11,6 +11,7 @@ NX_EXPLORER_ASSETS=1
|
||||
NX_EXPLORER_GENESIS=1
|
||||
NX_EXPLORER_GOVERNANCE=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
|
||||
@@ -11,6 +11,7 @@ NX_EXPLORER_ASSETS=1
|
||||
NX_EXPLORER_GENESIS=1
|
||||
NX_EXPLORER_GOVERNANCE=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
|
||||
@@ -11,6 +11,7 @@ NX_EXPLORER_ASSETS=1
|
||||
NX_EXPLORER_GENESIS=1
|
||||
NX_EXPLORER_GOVERNANCE=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
|
||||
@@ -11,6 +11,7 @@ NX_EXPLORER_ASSETS=1
|
||||
NX_EXPLORER_GENESIS=1
|
||||
NX_EXPLORER_GOVERNANCE=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
|
||||
@@ -23,4 +23,5 @@ NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
NX_EXPLORER_VALIDATORS=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=1
|
||||
|
||||
@@ -13,6 +13,7 @@ NX_EXPLORER_ASSETS=1
|
||||
NX_EXPLORER_GENESIS=1
|
||||
NX_EXPLORER_GOVERNANCE=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
|
||||
@@ -58,6 +58,7 @@ There are a few different configuration options offered for this app:
|
||||
| `NX_EXPLORER_GENESIS` | Enable the genesis page for the explorer |
|
||||
| `NX_EXPLORER_GOVERNANCE` | Enable the governance page for the explorer |
|
||||
| `NX_EXPLORER_MARKETS` | Enable the markets page for the explorer |
|
||||
| `NX_EXPLORER_ORACLES` | Enable the oracles page for the explorer |
|
||||
| `NX_EXPLORER_TXS_LIST` | Enable the transactions list page for the explorer |
|
||||
| `NX_EXPLORER_NETWORK_PARAMETERS` | Enable the network parameters page for the explorer |
|
||||
| `NX_EXPLORER_PARTIES` | Enable the parties page for the explorer |
|
||||
|
||||
@@ -2,7 +2,9 @@ import React from 'react';
|
||||
import type { BlockMeta } from '../../routes/blocks/tendermint-blockchain-response';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { SecondsAgo } from '../seconds-ago';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { TimeAgo } from '../time-ago';
|
||||
import { TableWithTbody, TableRow, TableCell } from '../table';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
@@ -53,7 +55,16 @@ export const BlockData = ({ block, className }: BlockProps) => {
|
||||
className="text-center pr-28 text-neutral-300 w-[170px]"
|
||||
aria-label={t('Block genesis')}
|
||||
>
|
||||
<SecondsAgo date={block.header?.time} />
|
||||
<Tooltip
|
||||
description={getDateTimeFormat().format(
|
||||
new Date(block.header.time)
|
||||
)}
|
||||
>
|
||||
{/*For some reason we get forwardRef errors if we pass in the TimeAgo component directly*/}
|
||||
<span>
|
||||
<TimeAgo date={block.header.time} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
|
||||
@@ -29,6 +29,7 @@ export const JumpToBlock = () => {
|
||||
inputType="number"
|
||||
inputName="blockNumber"
|
||||
submitHandler={handleSubmit}
|
||||
inputMin={1}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,6 +9,8 @@ interface JumpToProps {
|
||||
inputType: HTMLInputTypeAttribute;
|
||||
inputName: string;
|
||||
submitHandler: (arg0: SyntheticEvent) => void;
|
||||
inputMin?: string | number;
|
||||
inputMax?: string | number;
|
||||
}
|
||||
|
||||
export const JumpTo = ({
|
||||
@@ -18,6 +20,8 @@ export const JumpTo = ({
|
||||
inputType,
|
||||
inputName,
|
||||
submitHandler,
|
||||
inputMin,
|
||||
inputMax,
|
||||
}: JumpToProps) => {
|
||||
return (
|
||||
<form onSubmit={submitHandler}>
|
||||
@@ -35,6 +39,8 @@ export const JumpTo = ({
|
||||
name={inputName}
|
||||
placeholder={placeholder}
|
||||
className="max-w-[200px]"
|
||||
min={inputMin}
|
||||
max={inputMax}
|
||||
/>
|
||||
<Button
|
||||
data-testid="go-submit"
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface SecondsAgoProps {
|
||||
date: string | undefined;
|
||||
}
|
||||
|
||||
export const SecondsAgo = ({ date, ...props }: SecondsAgoProps) => {
|
||||
const [now, setNow] = useState(Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
const int = setInterval(() => {
|
||||
setNow(Date.now());
|
||||
}, 500);
|
||||
return () => clearInterval(int);
|
||||
}, [setNow]);
|
||||
|
||||
if (!date) {
|
||||
return <>{t('Date unknown')}</>;
|
||||
}
|
||||
|
||||
const timeAgoInSeconds = Math.floor((now - new Date(date).getTime()) / 1000);
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
{t(
|
||||
`${
|
||||
timeAgoInSeconds === 1 ? '1 second' : `${timeAgoInSeconds} seconds`
|
||||
} ago`
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
import { SecondsAgo } from './index';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe('Seconds ago', () => {
|
||||
it('should render successfully', () => {
|
||||
const dateInString = new Date().toString();
|
||||
render(<SecondsAgo data-testid="test-seconds-ago" date={dateInString} />);
|
||||
|
||||
expect(screen.getByTestId('test-seconds-ago')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show the correct amount of seconds ago', () => {
|
||||
const secondsToWait = 10;
|
||||
const dateInString = new Date().toString();
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(secondsToWait * 1000);
|
||||
});
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
render(<SecondsAgo data-testid="test-seconds-ago" date={dateInString} />);
|
||||
|
||||
expect(screen.getByTestId('test-seconds-ago')).toHaveTextContent(
|
||||
`${secondsToWait} seconds ago`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { formatDistanceToNowStrict } from 'date-fns';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface TimeAgoProps {
|
||||
date: string;
|
||||
}
|
||||
|
||||
export const TimeAgo = ({ date, ...props }: TimeAgoProps) => {
|
||||
const [distanceToNow, setDistanceToNow] = useState(
|
||||
formatDistanceToNowStrict(new Date(date))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const int = setInterval(() => {
|
||||
date && setDistanceToNow(formatDistanceToNowStrict(new Date(date)));
|
||||
}, 500);
|
||||
return () => clearInterval(int);
|
||||
}, [setDistanceToNow, date]);
|
||||
|
||||
if (!date) {
|
||||
return <>{t('Date unknown')}</>;
|
||||
}
|
||||
|
||||
return <span {...props}>{t(`${distanceToNow} ago`)}</span>;
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
import { TimeAgo } from './index';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe('Time ago', () => {
|
||||
it('should render successfully', () => {
|
||||
const dateString = new Date().toString();
|
||||
render(<TimeAgo data-testid="date" date={dateString} />);
|
||||
|
||||
expect(screen.getByTestId('date')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show the correct amount of time ago', () => {
|
||||
const secondsToWait = 10;
|
||||
const date = new Date(-(secondsToWait * 1000)).toString();
|
||||
|
||||
render(<TimeAgo data-testid="test-time-ago" date={date} />);
|
||||
|
||||
expect(screen.getByTestId('test-time-ago')).toHaveTextContent(
|
||||
`${secondsToWait} seconds ago`
|
||||
);
|
||||
});
|
||||
|
||||
it('should show the correct amount of time ago after time has advanced', () => {
|
||||
const date = new Date().toString();
|
||||
|
||||
render(<TimeAgo data-testid="test-time-elapsed" date={date} />);
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(30000);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('test-time-elapsed')).toHaveTextContent(
|
||||
`30 seconds ago`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -24,6 +24,7 @@ export const ENV = {
|
||||
genesis: truthy.includes(windowOrDefault('NX_EXPLORER_GENESIS')),
|
||||
governance: truthy.includes(windowOrDefault('NX_EXPLORER_GOVERNANCE')),
|
||||
markets: truthy.includes(windowOrDefault('NX_EXPLORER_MARKETS')),
|
||||
oracles: truthy.includes(windowOrDefault('NX_EXPLORER_ORACLES')),
|
||||
txsList: truthy.includes(windowOrDefault('NX_EXPLORER_TXS_LIST')),
|
||||
networkParameters: truthy.includes(
|
||||
windowOrDefault('NX_EXPLORER_NETWORK_PARAMETERS')
|
||||
|
||||
@@ -174,7 +174,7 @@ describe('Block', () => {
|
||||
);
|
||||
expect(proposer).toHaveAttribute('href', `/${RouteNames.VALIDATORS}`);
|
||||
expect(screen.getByTestId('block-time')).toHaveTextContent(
|
||||
'3528 seconds ago'
|
||||
'59 minutes ago'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
|
||||
import type { TendermintBlocksResponse } from '../tendermint-blocks-response';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { SecondsAgo } from '../../../components/seconds-ago';
|
||||
import { TimeAgo } from '../../../components/time-ago';
|
||||
import {
|
||||
TableWithTbody,
|
||||
TableRow,
|
||||
@@ -53,30 +54,38 @@ const Block = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<TableWithTbody className="mb-28">
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Mined by</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<HighlightedLink
|
||||
to={`/${Routes.VALIDATORS}`}
|
||||
text={blockData?.result.block.header.proposer_address}
|
||||
data-testid="block-validator"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Time</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<SecondsAgo
|
||||
data-testid="block-time"
|
||||
date={blockData?.result.block.header.time}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
{blockData && blockData.result.block.data.txs.length > 0 ? (
|
||||
<TxsPerBlock blockHeight={block} />
|
||||
) : null}
|
||||
{blockData && (
|
||||
<>
|
||||
<TableWithTbody className="mb-28">
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Mined by</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<HighlightedLink
|
||||
to={`/${Routes.VALIDATORS}`}
|
||||
text={blockData.result.block.header.proposer_address}
|
||||
data-testid="block-validator"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Time</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<TimeAgo
|
||||
data-testid="block-time"
|
||||
date={blockData.result.block.header.time}
|
||||
/>{' '}
|
||||
-{' '}
|
||||
{getDateTimeFormat().format(
|
||||
new Date(blockData.result.block.header.time)
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
{blockData.result.block.data.txs.length > 0 ? (
|
||||
<TxsPerBlock blockHeight={block} />
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</RenderFetched>
|
||||
</section>
|
||||
|
||||
@@ -108,7 +108,9 @@ const Governance = () => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="governance-header">{t('Governance')}</RouteTitle>
|
||||
<RouteTitle data-testid="governance-header">
|
||||
{t('Governance Proposals')}
|
||||
</RouteTitle>
|
||||
{data.proposals?.map((p) => (
|
||||
<React.Fragment key={p.id}>
|
||||
<SubHeading>{getProposalName(p.terms.change)}</SubHeading>
|
||||
|
||||
@@ -24,10 +24,10 @@ describe('NetworkParametersTable', () => {
|
||||
);
|
||||
const rows = screen.getAllByTestId('key-value-table-row');
|
||||
expect(rows[0].children[0]).toHaveTextContent(
|
||||
'Market Fee Factors Infrastructure Fee'
|
||||
'market.fee.factors.infrastructureFee'
|
||||
);
|
||||
expect(rows[1].children[0]).toHaveTextContent(
|
||||
'Market Liquidity Provision Min Lp Stake Quantum Multiple'
|
||||
'market.liquidityProvision.minLpStakeQuantumMultiple'
|
||||
);
|
||||
expect(rows[0].children[1]).toHaveTextContent('0.0005');
|
||||
expect(rows[1].children[1]).toHaveTextContent('1');
|
||||
@@ -54,10 +54,10 @@ describe('NetworkParametersTable', () => {
|
||||
);
|
||||
const rows = screen.getAllByTestId('key-value-table-row');
|
||||
expect(rows[0].children[0]).toHaveTextContent(
|
||||
'Market Fee Factors Infrastructure Fee'
|
||||
'market.fee.factors.infrastructureFee'
|
||||
);
|
||||
expect(rows[1].children[0]).toHaveTextContent(
|
||||
'Market Liquidity Provision Min Lp Stake Quantum Multiple'
|
||||
'market.liquidityProvision.minLpStakeQuantumMultiple'
|
||||
);
|
||||
expect(rows[0].children[1]).toHaveTextContent('0.0005');
|
||||
expect(rows[1].children[1]).toHaveTextContent('1');
|
||||
|
||||
@@ -16,7 +16,6 @@ import type {
|
||||
NetworkParametersQuery_networkParameters,
|
||||
} from './__generated__/NetworkParametersQuery';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import startCase from 'lodash/startCase';
|
||||
|
||||
const BIG_NUMBER_PARAMS = [
|
||||
'spam.protection.delegation.min.tokens',
|
||||
@@ -42,8 +41,15 @@ export const renderRow = ({
|
||||
}: NetworkParametersQuery_networkParameters) => {
|
||||
const isSyntaxRow = isJsonObject(value);
|
||||
return (
|
||||
<KeyValueTableRow key={key} inline={!isSyntaxRow}>
|
||||
{startCase(key)}
|
||||
<KeyValueTableRow
|
||||
key={key}
|
||||
inline={!isSyntaxRow}
|
||||
id={key}
|
||||
className={
|
||||
'group target:bg-vega-pink target:text-white dark:target:bg-vega-yellow dark:target:text-black'
|
||||
}
|
||||
>
|
||||
{key}
|
||||
{isSyntaxRow ? (
|
||||
<SyntaxHighlighter data={JSON.parse(value)} />
|
||||
) : isNaN(Number(value)) ? (
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { OracleSpecStatus, PropertyKeyType, ConditionOperator } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: OracleSpecs
|
||||
// ====================================================
|
||||
|
||||
export interface OracleSpecs_oracleSpecs_filters_key {
|
||||
__typename: "PropertyKey";
|
||||
/**
|
||||
* name is the name of the property.
|
||||
*/
|
||||
name: string | null;
|
||||
/**
|
||||
* type is the type of the property.
|
||||
*/
|
||||
type: PropertyKeyType;
|
||||
}
|
||||
|
||||
export interface OracleSpecs_oracleSpecs_filters_conditions {
|
||||
__typename: "Condition";
|
||||
/**
|
||||
* value is used by the comparator.
|
||||
*/
|
||||
value: string | null;
|
||||
/**
|
||||
* comparator is the type of comparison to make on the value.
|
||||
*/
|
||||
operator: ConditionOperator;
|
||||
}
|
||||
|
||||
export interface OracleSpecs_oracleSpecs_filters {
|
||||
__typename: "Filter";
|
||||
/**
|
||||
* key is the oracle data property key targeted by the filter.
|
||||
*/
|
||||
key: OracleSpecs_oracleSpecs_filters_key;
|
||||
/**
|
||||
* conditions are the conditions that should be matched by the data to be
|
||||
* considered of interest.
|
||||
*/
|
||||
conditions: OracleSpecs_oracleSpecs_filters_conditions[] | null;
|
||||
}
|
||||
|
||||
export interface OracleSpecs_oracleSpecs_data {
|
||||
__typename: "OracleData";
|
||||
/**
|
||||
* pubKeys is the list of public keys that signed the data
|
||||
*/
|
||||
pubKeys: string[] | null;
|
||||
}
|
||||
|
||||
export interface OracleSpecs_oracleSpecs {
|
||||
__typename: "OracleSpec";
|
||||
/**
|
||||
* status describes the status of the oracle spec
|
||||
*/
|
||||
status: OracleSpecStatus;
|
||||
/**
|
||||
* id is a hash generated from the OracleSpec data.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* RFC3339Nano creation date time
|
||||
*/
|
||||
createdAt: string;
|
||||
/**
|
||||
* RFC3339Nano last updated timestamp
|
||||
*/
|
||||
updatedAt: string | null;
|
||||
/**
|
||||
* pubKeys is the list of authorized public keys that signed the data for this
|
||||
* oracle. All the public keys in the oracle data should be contained in these
|
||||
* public keys.
|
||||
*/
|
||||
pubKeys: string[] | null;
|
||||
/**
|
||||
* filters describes which oracle data are considered of interest or not for
|
||||
* the product (or the risk model).
|
||||
*/
|
||||
filters: OracleSpecs_oracleSpecs_filters[] | null;
|
||||
/**
|
||||
* data list all the oracle data broadcast to this spec
|
||||
*/
|
||||
data: OracleSpecs_oracleSpecs_data[];
|
||||
}
|
||||
|
||||
export interface OracleSpecs {
|
||||
/**
|
||||
* All registered oracle specs
|
||||
*/
|
||||
oracleSpecs: OracleSpecs_oracleSpecs[] | null;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import type { OracleSpecs as OracleSpecsQuery } from './__generated__/OracleSpecs';
|
||||
|
||||
import React from 'react';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { SubHeading } from '../../components/sub-heading';
|
||||
|
||||
const ORACLE_SPECS_QUERY = gql`
|
||||
query OracleSpecs {
|
||||
oracleSpecs {
|
||||
status
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
pubKeys
|
||||
filters {
|
||||
key {
|
||||
name
|
||||
type
|
||||
}
|
||||
conditions {
|
||||
value
|
||||
operator
|
||||
}
|
||||
}
|
||||
data {
|
||||
pubKeys
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Oracles = () => {
|
||||
const { data } = useQuery<OracleSpecsQuery>(ORACLE_SPECS_QUERY);
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
|
||||
{data?.oracleSpecs
|
||||
? data.oracleSpecs.map((o) => (
|
||||
<React.Fragment key={o.id}>
|
||||
<SubHeading>{o.id}</SubHeading>
|
||||
<SyntaxHighlighter data={o} />
|
||||
</React.Fragment>
|
||||
))
|
||||
: null}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Oracles;
|
||||
@@ -8,5 +8,6 @@ export const Routes = {
|
||||
GENESIS: 'genesis',
|
||||
GOVERNANCE: 'governance',
|
||||
MARKETS: 'markets',
|
||||
ORACLES: 'oracles',
|
||||
NETWORK_PARAMETERS: 'network-parameters',
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import BlockPage from './blocks';
|
||||
import Governance from './governance';
|
||||
import Home from './home';
|
||||
import Markets from './markets';
|
||||
import Oracles from './oracles';
|
||||
import Party from './parties';
|
||||
import { Parties } from './parties/home';
|
||||
import { Party as PartySingle } from './parties/id';
|
||||
@@ -66,8 +67,8 @@ const governanceRoutes = flags.governance
|
||||
? [
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
name: 'Governance',
|
||||
text: t('Proposals'),
|
||||
name: 'Governance proposals',
|
||||
text: t('Governance Proposals'),
|
||||
element: <Governance />,
|
||||
},
|
||||
]
|
||||
@@ -84,6 +85,17 @@ const marketsRoutes = flags.markets
|
||||
]
|
||||
: [];
|
||||
|
||||
const oraclesRoutes = flags.oracles
|
||||
? [
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
name: 'Oracles',
|
||||
text: t('Oracles'),
|
||||
element: <Oracles />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const networkParametersRoutes = flags.networkParameters
|
||||
? [
|
||||
{
|
||||
@@ -154,6 +166,7 @@ const routerConfig = [
|
||||
...genesisRoutes,
|
||||
...governanceRoutes,
|
||||
...marketsRoutes,
|
||||
...oraclesRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
];
|
||||
|
||||
@@ -24,3 +24,4 @@ NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_VEGA_REST=https://lb.testnet.vega.xyz/datanode/rest
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
|
||||
|
||||
@@ -18,4 +18,22 @@ module.exports = defineConfig({
|
||||
viewportWidth: 1440,
|
||||
viewportHeight: 900,
|
||||
},
|
||||
env: {
|
||||
TRADING_TEST_VEGA_WALLET_NAME: 'UI_Trading_Test',
|
||||
ETHEREUM_PROVIDER_URL:
|
||||
'https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
|
||||
VEGA_PUBLIC_KEY:
|
||||
'47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278',
|
||||
VEGA_PUBLIC_KEY2:
|
||||
'1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4',
|
||||
TRUNCATED_VEGA_PUBLIC_KEY: '47836c…c7d278',
|
||||
TRUNCATED_VEGA_PUBLIC_KEY2: '1a18cd…0cf2e4',
|
||||
ETHEREUM_WALLET_ADDRESS: '0x265Cc6d39a1B53d0d92068443009eE7410807158',
|
||||
ETHERSCAN_URL: 'https://ropsten.etherscan.io',
|
||||
tsConfig: 'tsconfig.json',
|
||||
TAGS: 'not @todo and not @ignore and not @manual',
|
||||
TRADING_TEST_VEGA_WALLET_PASSPHRASE: '123',
|
||||
ETH_WALLET_MNEMONIC:
|
||||
'ugly gallery notice network true range brave clarify flat logic someone chunk',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
import { aliasQuery } from '@vegaprotocol/cypress';
|
||||
import { generateSimpleMarkets } from '../support/mocks/generate-markets';
|
||||
import { generateDealTicket } from '../support/mocks/generate-deal-ticket';
|
||||
import { generateMarketTags } from '../support/mocks/generate-market-tags';
|
||||
import { generateMarketPositions } from '../support/mocks/generate-market-positions';
|
||||
import { generateEstimateOrder } from '../support/mocks/generate-estimate-order';
|
||||
import { generatePartyBalance } from '../support/mocks/generate-party-balance';
|
||||
|
||||
const connectVegaWallet = () => {
|
||||
const form = 'rest-connector-form';
|
||||
const walletName = Cypress.env('TRADING_TEST_VEGA_WALLET_NAME');
|
||||
const walletPassphrase = Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE');
|
||||
|
||||
cy.getByTestId('connect-vega-wallet').click();
|
||||
cy.getByTestId('connectors-list').find('button').click();
|
||||
cy.getByTestId(form).find('#wallet').click().type(walletName);
|
||||
cy.getByTestId(form).find('#passphrase').click().type(walletPassphrase);
|
||||
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
|
||||
};
|
||||
|
||||
describe('Market trade', () => {
|
||||
let markets;
|
||||
@@ -8,6 +24,10 @@ describe('Market trade', () => {
|
||||
cy.mockGQL((req) => {
|
||||
aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets());
|
||||
aliasQuery(req, 'DealTicketQuery', generateDealTicket());
|
||||
aliasQuery(req, 'MarketTags', generateMarketTags());
|
||||
aliasQuery(req, 'MarketPositions', generateMarketPositions());
|
||||
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
|
||||
aliasQuery(req, 'PartyBalanceQuery', generatePartyBalance());
|
||||
});
|
||||
cy.visit('/markets');
|
||||
cy.wait('@SimpleMarkets').then((response) => {
|
||||
@@ -64,4 +84,32 @@ describe('Market trade', () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('order review should display proper calculations', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
connectVegaWallet();
|
||||
cy.get('h3').contains('Review Trade').click();
|
||||
cy.getByTestId('key-value-table')
|
||||
.find('dl')
|
||||
.eq(1)
|
||||
.find('dd div')
|
||||
.should('have.text', '25.78726');
|
||||
cy.getByTestId('key-value-table')
|
||||
.find('dl')
|
||||
.eq(2)
|
||||
.find('dd div')
|
||||
.should('have.text', '1.00000');
|
||||
cy.getByTestId('key-value-table')
|
||||
.find('dl')
|
||||
.eq(3)
|
||||
.find('dd div')
|
||||
.should('have.text', '-785.81045');
|
||||
cy.getByTestId('place-order').click();
|
||||
cy.getByTestId('dialog-title').should(
|
||||
'have.text',
|
||||
'Confirm transaction in wallet'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export const generateEstimateOrder = () => {
|
||||
return {
|
||||
estimateOrder: {
|
||||
totalFeeAmount: '16085.09240212.7380425.46',
|
||||
marginLevels: {
|
||||
initialLevel: '2844054.80937741220203',
|
||||
__typename: 'MarginLevels',
|
||||
},
|
||||
__typename: 'OrderEstimate',
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
export const generateMarketPositions = () => {
|
||||
return {
|
||||
party: {
|
||||
id: '2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
accounts: [
|
||||
{
|
||||
asset: {
|
||||
decimals: 5,
|
||||
},
|
||||
balance: '400000000000000000000',
|
||||
market: {
|
||||
id: '2751c508f9759761f912890f37fb3f97a00300bf7685c02a56a86e05facfe221',
|
||||
__typename: 'Market',
|
||||
},
|
||||
},
|
||||
{
|
||||
asset: {
|
||||
decimals: 5,
|
||||
},
|
||||
balance: '265329',
|
||||
market: {
|
||||
id: 'first-btcusd-id',
|
||||
__typename: 'Market',
|
||||
},
|
||||
},
|
||||
],
|
||||
positionsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
openVolume: '3',
|
||||
market: {
|
||||
id: '2751c508f9759761f912890f37fb3f97a00300bf7685c02a56a86e05facfe221',
|
||||
__typename: 'Market',
|
||||
},
|
||||
__typename: 'Position',
|
||||
},
|
||||
__typename: 'PositionEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
openVolume: '12',
|
||||
market: {
|
||||
id: 'first-btcusd-id',
|
||||
__typename: 'Market',
|
||||
},
|
||||
__typename: 'Position',
|
||||
},
|
||||
__typename: 'PositionEdge',
|
||||
},
|
||||
],
|
||||
__typename: 'PositionConnection',
|
||||
},
|
||||
__typename: 'Party',
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
export const generateMarketTags = () => {
|
||||
return {
|
||||
market: {
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
metadata: {
|
||||
tags: [
|
||||
'formerly:2839D9B2329C9E70',
|
||||
'base:AAVE',
|
||||
'quote:DAI',
|
||||
'class:fx/crypto',
|
||||
'monthly',
|
||||
'sector:defi',
|
||||
'settlement:2022-08-01',
|
||||
],
|
||||
__typename: 'InstrumentMetadata',
|
||||
},
|
||||
__typename: 'Instrument',
|
||||
},
|
||||
__typename: 'TradableInstrument',
|
||||
},
|
||||
__typename: 'Market',
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
export const generatePartyBalance = () => {
|
||||
return {
|
||||
party: {
|
||||
accounts: [
|
||||
{
|
||||
balance: '88474051',
|
||||
asset: {
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
symbol: 'tDAI',
|
||||
name: 'tDAI TEST',
|
||||
decimals: 5,
|
||||
__typename: 'Asset',
|
||||
},
|
||||
__typename: 'Account',
|
||||
},
|
||||
{
|
||||
balance: '100000000',
|
||||
asset: {
|
||||
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
|
||||
symbol: 'tEURO',
|
||||
name: 'tEURO TEST',
|
||||
decimals: 5,
|
||||
__typename: 'Asset',
|
||||
},
|
||||
__typename: 'Account',
|
||||
},
|
||||
{
|
||||
balance: '3412867',
|
||||
asset: {
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
symbol: 'tDAI',
|
||||
name: 'tDAI TEST',
|
||||
decimals: 5,
|
||||
__typename: 'Asset',
|
||||
},
|
||||
__typename: 'Account',
|
||||
},
|
||||
{
|
||||
balance: '70007',
|
||||
asset: {
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
symbol: 'tDAI',
|
||||
name: 'tDAI TEST',
|
||||
decimals: 5,
|
||||
__typename: 'Asset',
|
||||
},
|
||||
__typename: 'Account',
|
||||
},
|
||||
],
|
||||
__typename: 'Party',
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -21,3 +21,4 @@ NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
NX_VEGA_CONFIG_URL="https://static.vega.xyz/assets/testnet-network.json"
|
||||
NX_VEGA_ENV = 'TESTNET'
|
||||
NX_VEGA_URL="https://lb.testnet.vega.xyz/query"
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: MarketTags
|
||||
// ====================================================
|
||||
|
||||
export interface MarketTags_market_tradableInstrument_instrument_metadata {
|
||||
__typename: "InstrumentMetadata";
|
||||
/**
|
||||
* An arbitrary list of tags to associated to associate to the Instrument (string list)
|
||||
*/
|
||||
tags: string[] | null;
|
||||
}
|
||||
|
||||
export interface MarketTags_market_tradableInstrument_instrument {
|
||||
__typename: "Instrument";
|
||||
/**
|
||||
* Metadata for this instrument
|
||||
*/
|
||||
metadata: MarketTags_market_tradableInstrument_instrument_metadata;
|
||||
}
|
||||
|
||||
export interface MarketTags_market_tradableInstrument {
|
||||
__typename: "TradableInstrument";
|
||||
/**
|
||||
* An instance of or reference to a fully specified instrument.
|
||||
*/
|
||||
instrument: MarketTags_market_tradableInstrument_instrument;
|
||||
}
|
||||
|
||||
export interface MarketTags_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* An instance of or reference to a tradable instrument.
|
||||
*/
|
||||
tradableInstrument: MarketTags_market_tradableInstrument;
|
||||
}
|
||||
|
||||
export interface MarketTags {
|
||||
/**
|
||||
* An instrument that is trading on the VEGA network
|
||||
*/
|
||||
market: MarketTags_market | null;
|
||||
}
|
||||
|
||||
export interface MarketTagsVariables {
|
||||
marketId: string;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ export const DealTicketContainer = () => {
|
||||
isWalletConnected={!!keypair?.pub}
|
||||
/>
|
||||
)}
|
||||
<DealTicketSteps market={data.market} />
|
||||
<DealTicketSteps market={data.market} partyData={partyData} />
|
||||
</DealTicketManager>
|
||||
)}
|
||||
</Container>
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import * as React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { Stepper } from '../stepper';
|
||||
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
|
||||
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { DealTicketAmount, MarketSelector } from '@vegaprotocol/deal-ticket';
|
||||
import { InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
DealTicketAmount,
|
||||
getDialogTitle,
|
||||
getDialogIntent,
|
||||
getDialogIcon,
|
||||
MarketSelector,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
|
||||
@@ -11,17 +18,22 @@ import {
|
||||
getDefaultOrder,
|
||||
useOrderValidation,
|
||||
useOrderSubmit,
|
||||
OrderFeedback,
|
||||
} from '@vegaprotocol/orders';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import MarketNameRenderer from '../simple-market-list/simple-market-renderer';
|
||||
import SideSelector, { SIDE_NAMES } from './side-selector';
|
||||
import ReviewTrade from './review-trade';
|
||||
import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
|
||||
|
||||
interface DealTicketMarketProps {
|
||||
market: DealTicketQuery_market;
|
||||
partyData?: PartyBalanceQuery;
|
||||
}
|
||||
|
||||
export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
export const DealTicketSteps = ({
|
||||
market,
|
||||
partyData,
|
||||
}: DealTicketMarketProps) => {
|
||||
const navigate = useNavigate();
|
||||
const setMarket = useCallback(
|
||||
(marketId) => {
|
||||
@@ -45,6 +57,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
const orderType = watch('type');
|
||||
const orderTimeInForce = watch('timeInForce');
|
||||
const orderSide = watch('side');
|
||||
const order = watch();
|
||||
|
||||
const { message: invalidText, isDisabled } = useOrderValidation({
|
||||
step,
|
||||
@@ -54,7 +67,8 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
fieldErrors: errors,
|
||||
});
|
||||
|
||||
const { submit, transaction } = useOrderSubmit(market);
|
||||
const { submit, transaction, finalizedOrder, TransactionDialog } =
|
||||
useOrderSubmit(market);
|
||||
|
||||
const transactionStatus =
|
||||
transaction.status === VegaTxStatus.Requested ||
|
||||
@@ -101,7 +115,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
component: (
|
||||
<DealTicketAmount
|
||||
orderType={orderType}
|
||||
step={0.02}
|
||||
step={step}
|
||||
register={register}
|
||||
price={
|
||||
market.depth.lastTrade
|
||||
@@ -121,17 +135,20 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
{invalidText}
|
||||
</InputError>
|
||||
)}
|
||||
<Button
|
||||
className="w-full mb-8"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={transactionStatus === 'pending' || isDisabled}
|
||||
data-testid="place-order"
|
||||
<ReviewTrade
|
||||
market={market}
|
||||
isDisabled={isDisabled}
|
||||
transactionStatus={transactionStatus}
|
||||
order={order}
|
||||
partyData={partyData}
|
||||
/>
|
||||
<TransactionDialog
|
||||
title={getDialogTitle(finalizedOrder?.status)}
|
||||
intent={getDialogIntent(finalizedOrder?.status)}
|
||||
icon={getDialogIcon(finalizedOrder?.status)}
|
||||
>
|
||||
{transactionStatus === 'pending'
|
||||
? t('Pending...')
|
||||
: t('Place order')}
|
||||
</Button>
|
||||
<OrderFeedback transaction={transaction} order={finalizedOrder} />
|
||||
</TransactionDialog>
|
||||
</div>
|
||||
),
|
||||
disabled: true,
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import { addDecimal, formatNumber, t } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import { SIDE_NAMES } from './side-selector';
|
||||
import { useVegaWallet, VegaWalletOrderSide } from '@vegaprotocol/wallet';
|
||||
import SimpleMarketExpires from '../simple-market-list/simple-market-expires';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import type {
|
||||
MarketTags,
|
||||
MarketTagsVariables,
|
||||
} from './__generated__/MarketTags';
|
||||
import useOrderMargin from '../../hooks/use-order-margin';
|
||||
import useOrderCloseOut from '../../hooks/use-order-closeout';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
|
||||
|
||||
export const MARKET_TAGS_QUERY = gql`
|
||||
query MarketTags($marketId: ID!) {
|
||||
market(id: $marketId) {
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
metadata {
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
market: DealTicketQuery_market;
|
||||
isDisabled: boolean;
|
||||
transactionStatus?: string;
|
||||
order: Order;
|
||||
partyData?: PartyBalanceQuery;
|
||||
}
|
||||
|
||||
export default ({
|
||||
isDisabled,
|
||||
market,
|
||||
order,
|
||||
transactionStatus,
|
||||
partyData,
|
||||
}: Props) => {
|
||||
const { keypair } = useVegaWallet();
|
||||
const { data: tagsData } = useQuery<MarketTags, MarketTagsVariables>(
|
||||
MARKET_TAGS_QUERY,
|
||||
{
|
||||
variables: { marketId: market.id },
|
||||
}
|
||||
);
|
||||
const estMargin = useOrderMargin({
|
||||
order,
|
||||
market,
|
||||
partyId: keypair?.pub || '',
|
||||
});
|
||||
const estCloseOut = useOrderCloseOut({ order, market, partyData });
|
||||
return (
|
||||
<div className="mb-8 text-black dark:text-white">
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow noBorder>
|
||||
<div className="flex flex-none gap-x-5 items-center">
|
||||
<div
|
||||
className={classNames(
|
||||
{
|
||||
'buyButton dark:buyButtonDark':
|
||||
order.side === VegaWalletOrderSide.Buy,
|
||||
'sellButton dark:sellButtonDark':
|
||||
order.side === VegaWalletOrderSide.Sell,
|
||||
},
|
||||
'px-8 py-4 inline text-ui-small'
|
||||
)}
|
||||
>
|
||||
{SIDE_NAMES[order.side]}
|
||||
</div>
|
||||
<div>{market.tradableInstrument.instrument.product.quoteName}</div>
|
||||
<div>
|
||||
{tagsData?.market?.tradableInstrument.instrument.metadata
|
||||
.tags && (
|
||||
<SimpleMarketExpires
|
||||
tags={
|
||||
tagsData?.market.tradableInstrument.instrument.metadata.tags
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-blue">
|
||||
@{' '}
|
||||
{market.depth.lastTrade
|
||||
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
||||
: ' - '}{' '}
|
||||
<span className="text-ui-small inline">(EST)</span>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder>
|
||||
<>{t('Est. margin')}</>
|
||||
<div className="text-black dark:text-white flex gap-x-5 items-center">
|
||||
{estMargin}
|
||||
<Icon name={IconNames.ISSUE} className="rotate-180" />
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder>
|
||||
<>
|
||||
{t('Size')}{' '}
|
||||
<div className="text-ui-small inline">
|
||||
({market.tradableInstrument.instrument.product.quoteName})
|
||||
</div>
|
||||
</>
|
||||
<div className="text-black dark:text-white flex gap-x-5 items-center">
|
||||
{formatNumber(order.size, market.decimalPlaces)}
|
||||
<Icon name={IconNames.ISSUE} className="rotate-180" />
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder>
|
||||
<>{t('Est. close out')}</>
|
||||
<div className="text-black dark:text-white flex gap-x-5 items-center">
|
||||
{estCloseOut}
|
||||
<Icon name={IconNames.ISSUE} className="rotate-180" />
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
|
||||
<Button
|
||||
className="w-full !py-8 mt-64 max-w-sm"
|
||||
boxShadow={false}
|
||||
variant="secondary"
|
||||
type="submit"
|
||||
disabled={transactionStatus === 'pending' || isDisabled}
|
||||
data-testid="place-order"
|
||||
appendIconName="arrow-top-right"
|
||||
>
|
||||
{transactionStatus === 'pending' ? t('Pending...') : t('Submit')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -29,6 +29,7 @@ export default ({ value, onSelect }: SideSelectorProps) => {
|
||||
variant="inline-link"
|
||||
aria-label={t('Open long position')}
|
||||
className={classNames(
|
||||
'py-8',
|
||||
'buyButton hover:buyButton dark:buyButtonDark dark:hover:buyButtonDark',
|
||||
{ selected: value === VegaWalletOrderSide.Buy }
|
||||
)}
|
||||
@@ -40,6 +41,7 @@ export default ({ value, onSelect }: SideSelectorProps) => {
|
||||
variant="inline-link"
|
||||
aria-label={t('Open short position')}
|
||||
className={classNames(
|
||||
'py-8',
|
||||
'sellButton hover:sellButton dark:sellButtonDark dark:hover:sellButtonDark',
|
||||
{ selected: value === VegaWalletOrderSide.Sell }
|
||||
)}
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ describe('SimpleMarketExpires', () => {
|
||||
'settlement-date:2022-04-25T1200',
|
||||
];
|
||||
render(<SimpleMarketExpires tags={tags} />);
|
||||
expect(screen.getByText('April 25')).toBeInTheDocument();
|
||||
expect(screen.getByText('Apr 25')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('last one proper tag should matter', () => {
|
||||
@@ -33,7 +33,7 @@ describe('SimpleMarketExpires', () => {
|
||||
'settlement-expiry-date:2022-03-25T12:00:00',
|
||||
];
|
||||
render(<SimpleMarketExpires tags={tags} />);
|
||||
expect(screen.getByText('March 25')).toBeInTheDocument();
|
||||
expect(screen.getByText('Mar 25')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('when no proper tag nor date should be null', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SimpleMarkets_markets } from '../components/simple-market-list/__generated__/SimpleMarkets';
|
||||
|
||||
export const DATE_FORMAT = 'dd MMMM yyyy HH:mm';
|
||||
export const EXPIRE_DATE_FORMAT = 'MMMM dd';
|
||||
export const EXPIRE_DATE_FORMAT = 'MMM dd';
|
||||
|
||||
export const TRADABLE_STATES = {
|
||||
Active: true,
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { Side, OrderTimeInForce, OrderType } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: EstimateOrder
|
||||
// ====================================================
|
||||
|
||||
export interface EstimateOrder_estimateOrder_marginLevels {
|
||||
__typename: "MarginLevels";
|
||||
/**
|
||||
* this is the minimal margin required for a party to place a new order on the network (unsigned int actually)
|
||||
*/
|
||||
initialLevel: string;
|
||||
}
|
||||
|
||||
export interface EstimateOrder_estimateOrder {
|
||||
__typename: "OrderEstimate";
|
||||
/**
|
||||
* The total estimated amount of fee if the order was to trade
|
||||
*/
|
||||
totalFeeAmount: string;
|
||||
/**
|
||||
* The margin requirement for this order
|
||||
*/
|
||||
marginLevels: EstimateOrder_estimateOrder_marginLevels;
|
||||
}
|
||||
|
||||
export interface EstimateOrder {
|
||||
/**
|
||||
* return an estimation of the potential cost for a new order
|
||||
*/
|
||||
estimateOrder: EstimateOrder_estimateOrder;
|
||||
}
|
||||
|
||||
export interface EstimateOrderVariables {
|
||||
marketId: string;
|
||||
partyId: string;
|
||||
price?: string | null;
|
||||
size: string;
|
||||
side: Side;
|
||||
timeInForce: OrderTimeInForce;
|
||||
expiration?: string | null;
|
||||
type: OrderType;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { AccountType } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: MarketPositions
|
||||
// ====================================================
|
||||
|
||||
export interface MarketPositions_party_accounts_asset {
|
||||
__typename: "Asset";
|
||||
/**
|
||||
* The precision of the asset
|
||||
*/
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_accounts_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_accounts {
|
||||
__typename: "Account";
|
||||
/**
|
||||
* Account type (General, Margin, etc)
|
||||
*/
|
||||
type: AccountType;
|
||||
/**
|
||||
* Balance as string - current account balance (approx. as balances can be updated several times per second)
|
||||
*/
|
||||
balance: string;
|
||||
/**
|
||||
* Asset, the 'currency'
|
||||
*/
|
||||
asset: MarketPositions_party_accounts_asset;
|
||||
/**
|
||||
* Market (only relevant to margin accounts)
|
||||
*/
|
||||
market: MarketPositions_party_accounts_market | null;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_positionsConnection_edges_node_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_positionsConnection_edges_node {
|
||||
__typename: "Position";
|
||||
/**
|
||||
* Open volume (uint64)
|
||||
*/
|
||||
openVolume: string;
|
||||
/**
|
||||
* Market relating to this position
|
||||
*/
|
||||
market: MarketPositions_party_positionsConnection_edges_node_market;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_positionsConnection_edges {
|
||||
__typename: "PositionEdge";
|
||||
node: MarketPositions_party_positionsConnection_edges_node;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party_positionsConnection {
|
||||
__typename: "PositionConnection";
|
||||
/**
|
||||
* The positions in this connection
|
||||
*/
|
||||
edges: MarketPositions_party_positionsConnection_edges[] | null;
|
||||
}
|
||||
|
||||
export interface MarketPositions_party {
|
||||
__typename: "Party";
|
||||
/**
|
||||
* Party identifier
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Collateral accounts relating to a party
|
||||
*/
|
||||
accounts: MarketPositions_party_accounts[] | null;
|
||||
/**
|
||||
* Trading positions relating to a party
|
||||
*/
|
||||
positionsConnection: MarketPositions_party_positionsConnection;
|
||||
}
|
||||
|
||||
export interface MarketPositions {
|
||||
/**
|
||||
* An entity that is trading on the VEGA network
|
||||
*/
|
||||
party: MarketPositions_party | null;
|
||||
}
|
||||
|
||||
export interface MarketPositionsVariables {
|
||||
partyId: string;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import useMarketPositions from './use-market-positions';
|
||||
|
||||
let mockNotEmptyData = {
|
||||
party: {
|
||||
accounts: [
|
||||
{
|
||||
balance: '50001000000',
|
||||
asset: {
|
||||
decimals: 5,
|
||||
},
|
||||
market: {
|
||||
id: 'marketId',
|
||||
},
|
||||
},
|
||||
{
|
||||
balance: '700000000000000000000000000000',
|
||||
asset: {
|
||||
decimals: 5,
|
||||
},
|
||||
market: {
|
||||
id: 'someOtherMarketId',
|
||||
},
|
||||
},
|
||||
],
|
||||
positionsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
openVolume: '100002',
|
||||
market: {
|
||||
id: 'marketId',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
openVolume: '3',
|
||||
market: {
|
||||
id: 'someOtherMarketId',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@apollo/client', () => ({
|
||||
...jest.requireActual('@apollo/client'),
|
||||
useQuery: jest.fn(() => ({ data: mockNotEmptyData })),
|
||||
}));
|
||||
|
||||
describe('useOrderPosition Hook', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should return proper positive value', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useMarketPositions({ marketId: 'marketId', partyId: 'partyId' })
|
||||
);
|
||||
expect(result.current?.openVolume.toNumber()).toEqual(100002);
|
||||
expect(result.current?.balance.toString()).toEqual('50001000000');
|
||||
});
|
||||
|
||||
it('if balance equal 0 return null', () => {
|
||||
mockNotEmptyData = {
|
||||
party: {
|
||||
accounts: [
|
||||
{
|
||||
balance: '0',
|
||||
asset: {
|
||||
decimals: 5,
|
||||
},
|
||||
market: {
|
||||
id: 'marketId',
|
||||
},
|
||||
},
|
||||
],
|
||||
positionsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
openVolume: '2',
|
||||
market: {
|
||||
id: 'marketId',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const { result } = renderHook(() =>
|
||||
useMarketPositions({ marketId: 'marketId', partyId: 'partyId' })
|
||||
);
|
||||
expect(result.current).toBeNull();
|
||||
});
|
||||
|
||||
it('if no markets return null', () => {
|
||||
mockNotEmptyData = {
|
||||
party: {
|
||||
accounts: [
|
||||
{
|
||||
balance: '33330',
|
||||
asset: {
|
||||
decimals: 5,
|
||||
},
|
||||
market: {
|
||||
id: 'otherMarketId',
|
||||
},
|
||||
},
|
||||
],
|
||||
positionsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
openVolume: '2',
|
||||
market: {
|
||||
id: 'otherMarketId',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const { result } = renderHook(() =>
|
||||
useMarketPositions({ marketId: 'marketId', partyId: 'partyId' })
|
||||
);
|
||||
expect(result.current).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type {
|
||||
MarketPositions,
|
||||
MarketPositionsVariables,
|
||||
} from './__generated__/marketPositions';
|
||||
|
||||
const MARKET_POSITIONS_QUERY = gql`
|
||||
query MarketPositions($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
accounts {
|
||||
type
|
||||
balance
|
||||
asset {
|
||||
decimals
|
||||
}
|
||||
market {
|
||||
id
|
||||
}
|
||||
}
|
||||
positionsConnection {
|
||||
edges {
|
||||
node {
|
||||
openVolume
|
||||
market {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
marketId: string;
|
||||
partyId: string;
|
||||
}
|
||||
|
||||
export type PositionMargin = {
|
||||
openVolume: BigNumber;
|
||||
balance: BigNumber;
|
||||
balanceDecimals?: number;
|
||||
} | null;
|
||||
|
||||
export default ({ marketId, partyId }: Props): PositionMargin => {
|
||||
const { data } = useQuery<MarketPositions, MarketPositionsVariables>(
|
||||
MARKET_POSITIONS_QUERY,
|
||||
{
|
||||
pollInterval: 5000,
|
||||
variables: { partyId },
|
||||
skip: !partyId,
|
||||
}
|
||||
);
|
||||
|
||||
const account = data?.party?.accounts?.find(
|
||||
(nodes) => nodes.market?.id === marketId
|
||||
);
|
||||
|
||||
if (account) {
|
||||
const balance = new BigNumber(account.balance || 0);
|
||||
const openVolume = new BigNumber(
|
||||
data?.party?.positionsConnection?.edges?.find(
|
||||
(nodes) => nodes.node.market.id === marketId
|
||||
)?.node.openVolume || 0
|
||||
);
|
||||
if (!balance.isZero() && !openVolume.isZero()) {
|
||||
return {
|
||||
balance,
|
||||
balanceDecimals: account?.asset.decimals,
|
||||
openVolume,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import useOrderCloseOut from './use-order-closeout';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
|
||||
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
|
||||
describe('useOrderCloseOut Hook', () => {
|
||||
const order = { size: '2', side: 'SIDE_BUY' };
|
||||
const market = {
|
||||
decimalPlaces: 5,
|
||||
depth: {
|
||||
lastTrade: {
|
||||
price: '1000000',
|
||||
},
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
settlementAsset: {
|
||||
id: 'assetId',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const partyData = {
|
||||
party: {
|
||||
accounts: [
|
||||
{
|
||||
balance: '200000',
|
||||
asset: {
|
||||
id: 'assetId',
|
||||
decimals: 5,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
it('return proper buy value', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useOrderCloseOut({
|
||||
order: order as Order,
|
||||
market: market as DealTicketQuery_market,
|
||||
partyData: partyData as PartyBalanceQuery,
|
||||
})
|
||||
);
|
||||
expect(result.current).toEqual('9.00000');
|
||||
});
|
||||
|
||||
it('return proper sell value', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useOrderCloseOut({
|
||||
order: { ...order, side: 'SIDE_SELL' } as Order,
|
||||
market: market as DealTicketQuery_market,
|
||||
partyData: partyData as PartyBalanceQuery,
|
||||
})
|
||||
);
|
||||
expect(result.current).toEqual('11.00000');
|
||||
});
|
||||
|
||||
it('return proper empty value', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useOrderCloseOut({
|
||||
order: { ...order, side: 'SIDE_SELL' } as Order,
|
||||
market: market as DealTicketQuery_market,
|
||||
})
|
||||
);
|
||||
expect(result.current).toEqual(' - ');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
|
||||
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import { useSettlementAccount } from './use-settlement-account';
|
||||
import { VegaWalletOrderSide } from '@vegaprotocol/wallet';
|
||||
import { addDecimal, formatNumber } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface Props {
|
||||
order: Order;
|
||||
market: DealTicketQuery_market;
|
||||
partyData?: PartyBalanceQuery;
|
||||
}
|
||||
|
||||
const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
|
||||
const account = useSettlementAccount(
|
||||
market.tradableInstrument.instrument.product.settlementAsset.id,
|
||||
partyData?.party?.accounts || []
|
||||
);
|
||||
if (account?.balance && market.depth.lastTrade) {
|
||||
const price = new BigNumber(
|
||||
addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
||||
);
|
||||
const balance = new BigNumber(
|
||||
addDecimal(account.balance, account.asset.decimals)
|
||||
);
|
||||
const { size, side } = order;
|
||||
const bigOne = new BigNumber(1);
|
||||
return formatNumber(
|
||||
side === VegaWalletOrderSide.Buy
|
||||
? bigOne.minus(balance.div(price.times(size))).times(price)
|
||||
: bigOne.plus(balance.div(price.times(size))).times(price),
|
||||
market.decimalPlaces
|
||||
);
|
||||
}
|
||||
return ' - ';
|
||||
};
|
||||
|
||||
export default useOrderCloseOut;
|
||||
@@ -0,0 +1,106 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
|
||||
import type { PositionMargin } from './use-market-positions';
|
||||
import useOrderMargin from './use-order-margin';
|
||||
|
||||
let mockEstimateData = {
|
||||
estimateOrder: {
|
||||
marginLevels: {
|
||||
initialLevel: '200000',
|
||||
},
|
||||
},
|
||||
};
|
||||
jest.mock('@apollo/client', () => ({
|
||||
...jest.requireActual('@apollo/client'),
|
||||
useQuery: jest.fn(() => ({ data: mockEstimateData })),
|
||||
}));
|
||||
|
||||
let mockMarketPositions: PositionMargin = {
|
||||
openVolume: new BigNumber(1),
|
||||
balance: new BigNumber(100000),
|
||||
};
|
||||
jest.mock('./use-market-positions', () => jest.fn(() => mockMarketPositions));
|
||||
|
||||
describe('useOrderMargin Hook', () => {
|
||||
const order = {
|
||||
size: '2',
|
||||
side: 'SIDE_BUY',
|
||||
timeInForce: 'TIME_IN_FORCE_IOC',
|
||||
type: 'TYPE_MARKET',
|
||||
};
|
||||
const market = {
|
||||
id: 'marketId',
|
||||
depth: {
|
||||
lastTrade: {
|
||||
price: '1000000',
|
||||
},
|
||||
},
|
||||
};
|
||||
const partyId = 'partyId';
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('margin should be properly calculated', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useOrderMargin({
|
||||
order: order as Order,
|
||||
market: market as DealTicketQuery_market,
|
||||
partyId,
|
||||
})
|
||||
);
|
||||
expect(result.current).toEqual('100000');
|
||||
|
||||
const calledSize = new BigNumber(mockMarketPositions?.openVolume || 0)
|
||||
.plus(order.size)
|
||||
.toString();
|
||||
expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual(
|
||||
calledSize
|
||||
);
|
||||
});
|
||||
|
||||
it('if there is no positions initialMargin should not be subtracted', () => {
|
||||
mockMarketPositions = null;
|
||||
const { result } = renderHook(() =>
|
||||
useOrderMargin({
|
||||
order: order as Order,
|
||||
market: market as DealTicketQuery_market,
|
||||
partyId,
|
||||
})
|
||||
);
|
||||
expect(result.current).toEqual('200000');
|
||||
|
||||
expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual(
|
||||
order.size
|
||||
);
|
||||
});
|
||||
|
||||
it('if api fails, should return empty value', () => {
|
||||
mockEstimateData = {
|
||||
estimateOrder: {
|
||||
marginLevels: {
|
||||
initialLevel: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
const { result } = renderHook(() =>
|
||||
useOrderMargin({
|
||||
order: order as Order,
|
||||
market: market as DealTicketQuery_market,
|
||||
partyId,
|
||||
})
|
||||
);
|
||||
expect(result.current).toEqual(' - ');
|
||||
|
||||
const calledSize = new BigNumber(mockMarketPositions?.openVolume || 0)
|
||||
.plus(order.size)
|
||||
.toString();
|
||||
expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual(
|
||||
calledSize
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import type {
|
||||
EstimateOrder,
|
||||
EstimateOrderVariables,
|
||||
} from './__generated__/estimateOrder';
|
||||
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
|
||||
import { OrderTimeInForce, OrderType, Side } from '@vegaprotocol/types';
|
||||
import {
|
||||
VegaWalletOrderSide,
|
||||
VegaWalletOrderTimeInForce,
|
||||
VegaWalletOrderType,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import useMarketPositions from './use-market-positions';
|
||||
|
||||
export const ESTIMATE_ORDER_QUERY = gql`
|
||||
query EstimateOrder(
|
||||
$marketId: ID!
|
||||
$partyId: ID!
|
||||
$price: String
|
||||
$size: String!
|
||||
$side: Side!
|
||||
$timeInForce: OrderTimeInForce!
|
||||
$expiration: String
|
||||
$type: OrderType!
|
||||
) {
|
||||
estimateOrder(
|
||||
marketId: $marketId
|
||||
partyId: $partyId
|
||||
price: $price
|
||||
size: $size
|
||||
side: $side
|
||||
timeInForce: $timeInForce
|
||||
expiration: $expiration
|
||||
type: $type
|
||||
) {
|
||||
totalFeeAmount
|
||||
marginLevels {
|
||||
initialLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
order: Order;
|
||||
market: DealTicketQuery_market;
|
||||
partyId: string;
|
||||
}
|
||||
|
||||
const times: Record<VegaWalletOrderTimeInForce, OrderTimeInForce> = {
|
||||
[VegaWalletOrderTimeInForce.GTC]: OrderTimeInForce.GTC,
|
||||
[VegaWalletOrderTimeInForce.GTT]: OrderTimeInForce.GTT,
|
||||
[VegaWalletOrderTimeInForce.IOC]: OrderTimeInForce.IOC,
|
||||
[VegaWalletOrderTimeInForce.FOK]: OrderTimeInForce.FOK,
|
||||
[VegaWalletOrderTimeInForce.GFN]: OrderTimeInForce.GFN,
|
||||
[VegaWalletOrderTimeInForce.GFA]: OrderTimeInForce.GFA,
|
||||
};
|
||||
|
||||
const types: Record<VegaWalletOrderType, OrderType> = {
|
||||
[VegaWalletOrderType.Market]: OrderType.Market,
|
||||
[VegaWalletOrderType.Limit]: OrderType.Limit,
|
||||
};
|
||||
|
||||
const useOrderMargin = ({ order, market, partyId }: Props) => {
|
||||
const marketPositions = useMarketPositions({ marketId: market.id, partyId });
|
||||
const { data } = useQuery<EstimateOrder, EstimateOrderVariables>(
|
||||
ESTIMATE_ORDER_QUERY,
|
||||
{
|
||||
variables: {
|
||||
marketId: market.id,
|
||||
partyId,
|
||||
price: market.depth.lastTrade?.price,
|
||||
size: new BigNumber(marketPositions?.openVolume || 0)
|
||||
[order.side === VegaWalletOrderSide.Buy ? 'plus' : 'minus'](
|
||||
order.size
|
||||
)
|
||||
.toString(),
|
||||
side: order.side === VegaWalletOrderSide.Buy ? Side.Buy : Side.Sell,
|
||||
timeInForce: times[order.timeInForce],
|
||||
type: types[order.type],
|
||||
},
|
||||
skip:
|
||||
!partyId || !market.id || !order.size || !market.depth.lastTrade?.price,
|
||||
}
|
||||
);
|
||||
if (data?.estimateOrder.marginLevels.initialLevel) {
|
||||
return addDecimal(
|
||||
BigNumber.maximum(
|
||||
0,
|
||||
new BigNumber(data.estimateOrder.marginLevels.initialLevel).minus(
|
||||
marketPositions?.balance || 0
|
||||
)
|
||||
).toString(),
|
||||
market.decimalPlaces
|
||||
);
|
||||
}
|
||||
return ' - ';
|
||||
};
|
||||
|
||||
export default useOrderMargin;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,7 @@
|
||||
"tranche_end": "2022-11-26T13:48:10.000Z",
|
||||
"total_added": "100",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "33.64974949264333",
|
||||
"locked_amount": "31.73197932521563",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "100",
|
||||
@@ -242,7 +242,7 @@
|
||||
"tranche_end": "2022-10-12T00:53:20.000Z",
|
||||
"total_added": "1100",
|
||||
"total_removed": "673.04388635",
|
||||
"locked_amount": "232.909199010654485",
|
||||
"locked_amount": "211.813727168949774",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1000",
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"tranche_end": "2022-10-12T00:53:20.000Z",
|
||||
"total_added": "1010.000000000000000001",
|
||||
"total_removed": "668.4622323651",
|
||||
"locked_amount": "213.85302384576360370021173566717402337",
|
||||
"locked_amount": "194.48351312785388340019255793378995434",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1000",
|
||||
|
||||
+2
-1
@@ -12,7 +12,8 @@ NX_ETHEREUM_CHAIN_ID=1440
|
||||
NX_ETH_URL_CONNECT=1
|
||||
NX_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||
NX_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
CYPRESS_INCLUDE_FLOWS=true
|
||||
CYPRESS_INCLUDE_FLOWS=true
|
||||
|
||||
@@ -25,7 +25,8 @@ const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const txTimeout = { timeout: 40000 };
|
||||
const epochTimeout = { timeout: 10000 };
|
||||
|
||||
context('Staking Tab - with eth and vega wallets connected', function () {
|
||||
// Tests skipped because of change of the UI of data nodes
|
||||
context.skip('Staking Tab - with eth and vega wallets connected', function () {
|
||||
before('visit staking tab and connect vega wallet', function () {
|
||||
cy.vega_wallet_import();
|
||||
cy.visit('/');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const validatorList = '[data-testid="node-list-item-name"]';
|
||||
const validatorsGrid = '[data-testid="validators-grid"]';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const ethWalletAssociatedBalances =
|
||||
'[data-testid="eth-wallet-associated-balances"]';
|
||||
@@ -23,7 +23,7 @@ context(
|
||||
cy.ethereum_wallet_connect();
|
||||
cy.navigate_to('staking');
|
||||
cy.wait_for_spinner();
|
||||
cy.get(validatorList).first().invoke('text').as('validatorName');
|
||||
cy.get(validatorsGrid).should('be.visible');
|
||||
});
|
||||
|
||||
describe('Eth wallet - contains VEGA tokens', function () {
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
const walletContainer = '[data-testid="vega-wallet"]';
|
||||
const walletHeader = '[data-testid="wallet-header"] h1';
|
||||
const connectButton = '[data-testid="connect-vega"]';
|
||||
const getVegaLink = '[data-testid="link"]';
|
||||
const dialog = '[role="dialog"]';
|
||||
const dialogHeader = '[data-testid="dialog-title"]';
|
||||
const connectorsList = '[data-testid="connectors-list"]';
|
||||
const dialogCloseBtn = '[data-testid="dialog-close"]';
|
||||
const restConnectorForm = '[data-testid="rest-connector-form"]';
|
||||
const restUrl = '#url';
|
||||
const restWallet = '#wallet';
|
||||
const restPassphrase = '#passphrase';
|
||||
const restConnectBtn = '[type="submit"]';
|
||||
const accountNo = '[data-testid="vega-account-truncated"]';
|
||||
const walletName = '[data-testid="wallet-name"]';
|
||||
const currencyTitle = '[data-testid="currency-title"]';
|
||||
const currencyValue = '[data-testid="currency-value"]';
|
||||
const vegaUnstaked = '[data-testid="vega-wallet-balance-unstaked"] .text-right';
|
||||
const governanceBtn = '[href="/governance"]';
|
||||
const stakingBtn = '[href="/staking"]';
|
||||
const manageLink = '[data-testid="manage-vega-wallet"]';
|
||||
const dialogWalletName = `[data-testid="key-${Cypress.env(
|
||||
'vegaWalletPublicKey'
|
||||
)}"] h2`;
|
||||
const dialogVegaKey = '[data-testid="vega-public-key-full"]';
|
||||
const dialogDisconnectBtn = '[data-testid="disconnect"]';
|
||||
const copyPublicKeyBtn = '[data-testid="copy-vega-public-key"]';
|
||||
|
||||
context('Vega Wallet - verify elements on widget', function () {
|
||||
before('visit token home page', function () {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
before('wait for widget to load', function () {
|
||||
cy.get(walletContainer, { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
it('should have VEGA WALLET header visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(walletHeader)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Vega Wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Connect Vega button visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(connectButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet to use associated $VEGA');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Get a Vega wallet link visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(getVegaLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Get a Vega wallet')
|
||||
.and('have.attr', 'href', 'https://vega.xyz/wallet');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when connect button clicked', function () {
|
||||
before('click connect vega wallet button', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(connectButton).click();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Connect Vega header visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(dialogHeader)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect to your Vega Wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have REST connector visible on list', function () {
|
||||
cy.get(connectorsList).within(() => {
|
||||
cy.get('button').should('be.visible').and('have.text', 'rest provider');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have close button visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(dialogCloseBtn).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rest connector form opened', function () {
|
||||
before('click rest provider link', function () {
|
||||
cy.get(connectorsList).within(() => {
|
||||
cy.get('button').click();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have url field visible', function () {
|
||||
cy.get(restConnectorForm).within(() => {
|
||||
cy.get(restUrl)
|
||||
.should('be.visible')
|
||||
.and('have.value', 'http://localhost:1789/api/v1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have wallet field visible', function () {
|
||||
cy.get(restConnectorForm).within(() => {
|
||||
cy.get(restWallet).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have password field visible', function () {
|
||||
cy.get(restConnectorForm).within(() => {
|
||||
cy.get(restPassphrase).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have connect button visible', function () {
|
||||
cy.get(restConnectorForm).within(() => {
|
||||
cy.get(restConnectBtn).should('be.visible').and('have.text', 'Connect');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Connect Vega header visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(dialogHeader)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect to your Vega Wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have close button visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(dialogCloseBtn).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
// after('close dialog', function () {
|
||||
// cy.get(dialogCloseBtn).click().should('not.exist');
|
||||
// }); - to be changed when dialog state is fixed - https://github.com/vegaprotocol/frontend-monorepo/issues/838
|
||||
});
|
||||
|
||||
describe('when vega wallet connected', function () {
|
||||
before('connect vega wallet', function () {
|
||||
cy.vega_wallet_import();
|
||||
|
||||
// cy.vega_wallet_connect(); - to be changed when dialog state is fixed - https://github.com/vegaprotocol/frontend-monorepo/issues/838
|
||||
// then code below can be removed
|
||||
cy.get(restConnectorForm).within(() => {
|
||||
cy.get('#wallet').click().type(Cypress.env('vegaWalletName'));
|
||||
cy.get('#passphrase').click().type(Cypress.env('vegaWalletPassphrase'));
|
||||
cy.get('button').contains('Connect').click();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have VEGA WALLET header visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(walletHeader)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Vega Wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have truncated account number visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(accountNo)
|
||||
.should('be.visible')
|
||||
.and('have.text', Cypress.env('vegaWalletPublicKeyShort'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should have wallet name visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(walletName)
|
||||
.should('be.visible')
|
||||
.and('have.text', `${Cypress.env('vegaWalletName')} key 1`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Vega Associated currency title visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(currencyTitle)
|
||||
.should('be.visible')
|
||||
.and('have.text', `VEGAAssociated`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Vega Associated currency value visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(currencyValue)
|
||||
.should('be.visible')
|
||||
.and('have.text', `0.000000000000000000`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Unstaked value visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaUnstaked)
|
||||
.should('be.visible')
|
||||
.and('have.text', `0.000000000000000000`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Governance button visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(governanceBtn)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Governance');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Staking button visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(stakingBtn).should('be.visible').and('have.text', 'Staking');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Manage link visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(manageLink).should('be.visible').and('have.text', 'Manage');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when Manage dialog opened', function () {
|
||||
before('click Manage link', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(manageLink).click();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have SELECT A VEGA KEY dialog title visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(dialogHeader)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'SELECT A VEGA KEY');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have wallet name visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(dialogWalletName)
|
||||
.should('be.visible')
|
||||
.and('have.text', `${Cypress.env('vegaWalletName')} key 1`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have vega wallet public key visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(dialogVegaKey)
|
||||
.should('be.visible')
|
||||
.and('have.text', `${Cypress.env('vegaWalletPublicKey')}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have copy public key button visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(copyPublicKeyBtn).should('be.visible').and('have.text', 'Copy');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have close button visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(dialogCloseBtn).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have vega Disconnect all keys button visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(dialogDisconnectBtn)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Disconnect all keys');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to disconnect all keys', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(dialogDisconnectBtn).click();
|
||||
});
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(connectButton).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -75,7 +75,7 @@ Cypress.Commands.add(
|
||||
(stakingBridgeContract) => {
|
||||
cy.highlight('Tearing down staking tokens from vega wallet if present');
|
||||
cy.wrap(
|
||||
stakingBridgeContract.stakeBalance(ethWalletPubKey, vegaWalletPubKey),
|
||||
stakingBridgeContract.stake_balance(ethWalletPubKey, vegaWalletPubKey),
|
||||
{
|
||||
timeout: transactionTimeout,
|
||||
log: false,
|
||||
@@ -83,7 +83,7 @@ Cypress.Commands.add(
|
||||
).then((stake_amount) => {
|
||||
if (String(stake_amount) != '0') {
|
||||
cy.wrap(
|
||||
stakingBridgeContract.removeStake(stake_amount, vegaWalletPubKey),
|
||||
stakingBridgeContract.remove_stake(stake_amount, vegaWalletPubKey),
|
||||
{ timeout: transactionTimeout, log: false }
|
||||
).then((tx) => {
|
||||
cy.wait_for_transaction(tx);
|
||||
@@ -95,12 +95,12 @@ Cypress.Commands.add(
|
||||
|
||||
Cypress.Commands.add('vega_wallet_teardown_vesting', (vestingContract) => {
|
||||
cy.highlight('Tearing down vesting tokens from vega wallet if present');
|
||||
cy.wrap(vestingContract.stakeBalance(ethWalletPubKey, vegaWalletPubKey), {
|
||||
cy.wrap(vestingContract.stake_balance(ethWalletPubKey, vegaWalletPubKey), {
|
||||
timeout: transactionTimeout,
|
||||
log: false,
|
||||
}).then((vesting_amount) => {
|
||||
if (String(vesting_amount) != '0') {
|
||||
cy.wrap(vestingContract.removeStake(vesting_amount, vegaWalletPubKey), {
|
||||
cy.wrap(vestingContract.remove_stake(vesting_amount, vegaWalletPubKey), {
|
||||
timeout: transactionTimeout,
|
||||
log: false,
|
||||
}).then((tx) => {
|
||||
|
||||
@@ -8,6 +8,7 @@ NX_FAIRGROUND=false
|
||||
NX_IS_NEW_BRIDGE_CONTRACT=true
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}'
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -49,7 +49,7 @@ const AppContainer = () => {
|
||||
<AppLoader>
|
||||
<BalanceManager>
|
||||
<>
|
||||
<div className="app dark max-w-[1300px] mx-auto my-0 grid grid-rows-[min-content_1fr_min-content] min-h-full lg:border-l-1 lg:border-r-1 lg:border-white font-sans text-body lg:text-body-large text-white-80">
|
||||
<div className="app max-w-[1300px] mx-auto my-0 grid grid-rows-[min-content_1fr_min-content] min-h-full lg:border-l-1 lg:border-r-1 lg:border-white font-sans text-body lg:text-body-large text-white-80">
|
||||
<AppBanner />
|
||||
<TemplateSidebar sidebar={sideBar}>
|
||||
<AppRouter />
|
||||
|
||||
@@ -25,7 +25,7 @@ export const SplashLoader = ({ text = 'Loading' }: { text?: string }) => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>{text}</div>
|
||||
<div className="text-white">{text}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import type {
|
||||
} from './__generated__/Delegations';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useContracts } from '../../contexts/contracts/contracts-context';
|
||||
import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const DELEGATIONS_QUERY = gql`
|
||||
query Delegations($partyId: ID!) {
|
||||
@@ -117,7 +118,7 @@ export const usePollForDelegations = () => {
|
||||
.filter((a) => a.type === AccountType.General)
|
||||
.map((a) => {
|
||||
const isVega =
|
||||
a.asset.source.__typename === 'ERC20' &&
|
||||
isAssetTypeERC20(a.asset) &&
|
||||
a.asset.source.contractAddress === vegaToken.address;
|
||||
|
||||
return {
|
||||
@@ -131,10 +132,9 @@ export const usePollForDelegations = () => {
|
||||
),
|
||||
image: isVega ? vegaBlack : noIcon,
|
||||
border: isVega,
|
||||
address:
|
||||
a.asset.source.__typename === 'ERC20'
|
||||
? a.asset.source.contractAddress
|
||||
: undefined,
|
||||
address: isAssetTypeERC20(a.asset)
|
||||
? a.asset.source.contractAddress
|
||||
: undefined,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
|
||||
@@ -43,10 +43,16 @@ export const VegaWallet = () => {
|
||||
<h1 className="col-start-1 m-0">{t('vegaWallet')}</h1>
|
||||
{keypair && (
|
||||
<>
|
||||
<div className="sm:row-start-2 sm:col-start-1 sm:col-span-2 text-h6 mb-12">
|
||||
<div
|
||||
data-testid="wallet-name"
|
||||
className="sm:row-start-2 sm:col-start-1 sm:col-span-2 text-h6 mb-12"
|
||||
>
|
||||
{keypair.name}
|
||||
</div>
|
||||
<span className="sm:col-start-2 place-self-end font-mono pb-2 px-4">
|
||||
<span
|
||||
data-testid="vega-account-truncated"
|
||||
className="sm:col-start-2 place-self-end font-mono pb-2 px-4"
|
||||
>
|
||||
{truncateMiddle(keypair.pub)}
|
||||
</span>
|
||||
</>
|
||||
@@ -129,6 +135,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
|
||||
const footer = (
|
||||
<WalletCardActions>
|
||||
<Button
|
||||
data-testid="manage-vega-wallet"
|
||||
variant="inline-link"
|
||||
className="mt-4"
|
||||
onClick={() =>
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import { usePrevious } from './use-previous';
|
||||
import type { BigNumber } from '../lib/bignumber';
|
||||
import { theme as tailwindcss } from '@vegaprotocol/tailwindcss-config';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
const Colors = tailwindcss.colors;
|
||||
|
||||
const FLASH_DURATION = 1200; // Duration of flash animation in milliseconds
|
||||
@@ -13,7 +12,6 @@ export function useAnimateValue(
|
||||
) {
|
||||
const shouldAnimate = React.useRef(false);
|
||||
const previous = usePrevious(value);
|
||||
const [theme] = useThemeSwitcher();
|
||||
|
||||
React.useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -38,8 +36,7 @@ export function useAnimateValue(
|
||||
offset: 0.8,
|
||||
},
|
||||
{
|
||||
backgroundColor:
|
||||
theme === 'dark' ? Colors.white[60] : Colors.black[60],
|
||||
backgroundColor: Colors.white[60],
|
||||
color: Colors.white.DEFAULT,
|
||||
},
|
||||
],
|
||||
@@ -64,8 +61,7 @@ export function useAnimateValue(
|
||||
offset: 0.8,
|
||||
},
|
||||
{
|
||||
backgroundColor:
|
||||
theme === 'dark' ? Colors.white[60] : Colors.black[60],
|
||||
backgroundColor: Colors.white[60],
|
||||
color: Colors.white.DEFAULT,
|
||||
},
|
||||
],
|
||||
|
||||
-1
@@ -189,7 +189,6 @@ export const ProposalsListItemDetails = ({
|
||||
className="col-start-2 row-start-1 justify-self-end"
|
||||
data-testid="vote-status"
|
||||
>
|
||||
testing testing 123
|
||||
{voteStatus}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export { VoteDetails } from './vote-details';
|
||||
export { VOTE_VALUE_MAP } from './vote-types';
|
||||
|
||||
@@ -2,8 +2,7 @@ import { captureException, captureMessage } from '@sentry/minimal';
|
||||
import * as React from 'react';
|
||||
|
||||
import { VoteValue } from '../../../../__generated__/globalTypes';
|
||||
import { VOTE_VALUE_MAP } from './vote-types';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, VegaWalletVoteValue } from '@vegaprotocol/wallet';
|
||||
|
||||
export type Vote = {
|
||||
value: VoteValue;
|
||||
@@ -101,7 +100,7 @@ export function useUserVote(
|
||||
pubKey: keypair.pub,
|
||||
propagate: true,
|
||||
voteSubmission: {
|
||||
value: VOTE_VALUE_MAP[value],
|
||||
value: VegaWalletVoteValue[value],
|
||||
proposalId,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { VoteValue } from '../../../../__generated__/globalTypes';
|
||||
|
||||
export const VOTE_VALUE_MAP = {
|
||||
[VoteValue.Yes]: 'VALUE_YES',
|
||||
[VoteValue.No]: 'VALUE_NO',
|
||||
} as const;
|
||||
@@ -63,7 +63,7 @@ export const VestingTable = ({
|
||||
{formatNumber(associated)}
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
<div className="flex">
|
||||
<div className="flex border-white border">
|
||||
<div
|
||||
className="bg-vega-pink h-16"
|
||||
style={{ flex: lockedPercentage.toNumber() }}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { AccountType } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: Rewards
|
||||
// ====================================================
|
||||
@@ -45,6 +47,10 @@ export interface Rewards_party_rewardDetails_rewards_epoch {
|
||||
|
||||
export interface Rewards_party_rewardDetails_rewards {
|
||||
__typename: "Reward";
|
||||
/**
|
||||
* The type of reward
|
||||
*/
|
||||
rewardType: AccountType;
|
||||
/**
|
||||
* The asset this reward is paid in
|
||||
*/
|
||||
|
||||
@@ -30,6 +30,7 @@ export const REWARDS_QUERY = gql`
|
||||
symbol
|
||||
}
|
||||
rewards {
|
||||
rewardType
|
||||
asset {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -19,10 +19,6 @@ interface RewardInfoProps {
|
||||
rewardAssetId: string;
|
||||
}
|
||||
|
||||
// Note: For now the only reward type is Staking. We'll need this from the API
|
||||
// at a later date
|
||||
const DEFAULT_REWARD_TYPE = 'Staking';
|
||||
|
||||
export const RewardInfo = ({
|
||||
data,
|
||||
currVegaKey,
|
||||
@@ -119,7 +115,7 @@ export const RewardTable = ({ reward, delegations }: RewardTableProps) => {
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
{t('rewardType')}
|
||||
<span>{DEFAULT_REWARD_TYPE}</span>
|
||||
<span>{reward.rewardType}</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('yourStake')}
|
||||
|
||||
@@ -57,6 +57,8 @@ interface ValidatorRendererProps {
|
||||
data: { validator: { avatarUrl: string; name: string } };
|
||||
}
|
||||
|
||||
const stripNonDigits = (string: string) => string.replace(/\D/g, '');
|
||||
|
||||
const ValidatorRenderer = ({ data }: ValidatorRendererProps) => {
|
||||
const { avatarUrl, name } = data.validator;
|
||||
return (
|
||||
@@ -66,6 +68,7 @@ const ValidatorRenderer = ({ data }: ValidatorRendererProps) => {
|
||||
className="h-24 w-24 rounded-full mr-8"
|
||||
src={avatarUrl}
|
||||
alt={`Avatar icon for ${name}`}
|
||||
onError={(e) => (e.currentTarget.style.display = 'none')}
|
||||
/>
|
||||
)}
|
||||
{name}
|
||||
@@ -142,7 +145,7 @@ export const NodeList = ({ epoch }: NodeListProps) => {
|
||||
[TOTAL_STAKE_THIS_EPOCH]: formatNumber(stakedTotal, 2),
|
||||
[SHARE]: stakedTotalPercentage,
|
||||
[VALIDATOR_STAKE]: formatNumber(stakedOnNode, 2),
|
||||
[PENDING_STAKE]: pendingStake,
|
||||
[PENDING_STAKE]: formatNumber(pendingStake, 2),
|
||||
[RANKING_SCORE]: formatNumber(new BigNumber(rankingScore), 5),
|
||||
[STAKE_SCORE]: formatNumber(new BigNumber(stakeScore), 5),
|
||||
[PERFORMANCE_SCORE]: formatNumber(new BigNumber(performanceScore), 5),
|
||||
@@ -167,16 +170,34 @@ export const NodeList = ({ epoch }: NodeListProps) => {
|
||||
field: TOTAL_STAKE_THIS_EPOCH,
|
||||
headerName: t('totalStakeThisEpoch').toString(),
|
||||
},
|
||||
{ field: SHARE, headerName: t('share').toString() },
|
||||
{ field: VALIDATOR_STAKE, headerName: t('validatorStake').toString() },
|
||||
{ field: PENDING_STAKE, headerName: t('nextEpoch').toString() },
|
||||
{ field: RANKING_SCORE, headerName: t('rankingScore').toString() },
|
||||
{ field: STAKE_SCORE, headerName: t('stakeScore').toString() },
|
||||
{
|
||||
field: SHARE,
|
||||
headerName: t('share').toString(),
|
||||
},
|
||||
{
|
||||
field: VALIDATOR_STAKE,
|
||||
headerName: t('validatorStake').toString(),
|
||||
},
|
||||
{
|
||||
field: PENDING_STAKE,
|
||||
headerName: t('nextEpoch').toString(),
|
||||
},
|
||||
{
|
||||
field: RANKING_SCORE,
|
||||
headerName: t('rankingScore').toString(),
|
||||
},
|
||||
{
|
||||
field: STAKE_SCORE,
|
||||
headerName: t('stakeScore').toString(),
|
||||
},
|
||||
{
|
||||
field: PERFORMANCE_SCORE,
|
||||
headerName: t('performanceScore').toString(),
|
||||
},
|
||||
{ field: VOTING_POWER, headerName: t('votingPower').toString() },
|
||||
{
|
||||
field: VOTING_POWER,
|
||||
headerName: t('votingPower').toString(),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
@@ -184,6 +205,8 @@ export const NodeList = ({ epoch }: NodeListProps) => {
|
||||
const defaultColDef = useMemo(
|
||||
() => ({
|
||||
sortable: true,
|
||||
comparator: (a: string, b: string) =>
|
||||
parseFloat(stripNonDigits(a)) - parseFloat(stripNonDigits(b)),
|
||||
}),
|
||||
[]
|
||||
);
|
||||
@@ -206,7 +229,7 @@ export const NodeList = ({ epoch }: NodeListProps) => {
|
||||
event.columnApi.applyColumnState({
|
||||
state: [
|
||||
{
|
||||
colId: t('rankingScore'),
|
||||
colId: RANKING_SCORE,
|
||||
sort: 'desc',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -5,8 +5,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useAppState } from '../../contexts/app-state/app-state-context';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import { removeDecimal } from '../../lib/decimals';
|
||||
import type { UndelegateSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { UndelegateSubmissionBody } from '@vegaprotocol/vegawallet-service-api-client';
|
||||
|
||||
interface PendingStakeProps {
|
||||
pendingAmount: BigNumber;
|
||||
|
||||
@@ -26,11 +26,11 @@ import {
|
||||
Radio,
|
||||
RadioGroup,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type {
|
||||
DelegateSubmissionBody,
|
||||
UndelegateSubmissionBody,
|
||||
} from '@vegaprotocol/vegawallet-service-api-client';
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
export const PARTY_DELEGATIONS_QUERY = gql`
|
||||
query PartyDelegations($partyId: ID!) {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
NX_USE_ENV_OVERRIDES=0
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('vega wallet', () => {
|
||||
cy.getByTestId(form).find('#wallet').click().type('invalid name');
|
||||
cy.getByTestId(form).find('#passphrase').click().type('invalid password');
|
||||
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
|
||||
cy.getByTestId('form-error').should('have.text', 'Authentication failed');
|
||||
cy.getByTestId('form-error').should('have.text', 'Invalid credentials');
|
||||
});
|
||||
|
||||
it('doesnt connect with invalid fields', () => {
|
||||
|
||||
@@ -6,3 +6,4 @@ NX_ETHERSCAN_URL=https://ropsten.etherscan.io
|
||||
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"}
|
||||
NX_USE_ENV_OVERRIDES=1
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
|
||||
|
||||
@@ -63,7 +63,10 @@ export const Web3Content = ({
|
||||
if (connector?.connectEagerly && !('Cypress' in window)) {
|
||||
connector.connectEagerly();
|
||||
}
|
||||
}, [connector]);
|
||||
// wallet connect doesnt handle connectEagerly being called when connector is also in the
|
||||
// deps array.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
||||
@@ -28,7 +28,7 @@ function AppBody({ Component, pageProps }: AppProps) {
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<div className="h-full text-white relative text-black-60 dark:text-white-60 z-0 grid grid-rows-[min-content,1fr]">
|
||||
<div className="h-full relative text-black-60 dark:text-white-60 z-0 grid grid-rows-[min-content,1fr]">
|
||||
<AppLoader>
|
||||
<div className="flex items-stretch border-b-[7px] bg-black border-vega-pink dark:border-vega-yellow">
|
||||
<Navbar />
|
||||
@@ -41,7 +41,11 @@ function AppBody({ Component, pageProps }: AppProps) {
|
||||
store.setVegaWalletManageDialog(open);
|
||||
}}
|
||||
/>
|
||||
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
|
||||
<ThemeSwitcher
|
||||
onToggle={toggleTheme}
|
||||
className="-my-4"
|
||||
sunClassName="text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<main data-testid={pageProps.page} className="dark:bg-black">
|
||||
|
||||
@@ -16,14 +16,10 @@ const DEPOSIT_PAGE_QUERY = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
interface DepositContainerProps {
|
||||
assetId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches data required for the Deposit page
|
||||
*/
|
||||
export const DepositContainer = ({ assetId }: DepositContainerProps) => {
|
||||
export const DepositContainer = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
|
||||
return (
|
||||
@@ -41,7 +37,6 @@ export const DepositContainer = ({ assetId }: DepositContainerProps) => {
|
||||
return (
|
||||
<DepositManager
|
||||
assets={data.assets}
|
||||
initialAssetId={assetId}
|
||||
isFaucetable={VEGA_ENV !== 'MAINNET'}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,29 +1,12 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { useMemo } from 'react';
|
||||
import { Web3Container } from '../../../components/web3-container';
|
||||
import { DepositContainer } from './deposit-container';
|
||||
|
||||
const Deposit = () => {
|
||||
const { query } = useRouter();
|
||||
|
||||
// AssetId can be specified in the query string to allow link to deposit a particular asset
|
||||
const assetId = useMemo(() => {
|
||||
if (query.assetId && Array.isArray(query.assetId)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (Array.isArray(query.assetId)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return query.assetId;
|
||||
}, [query]);
|
||||
|
||||
return (
|
||||
<Web3Container>
|
||||
<div className="max-w-[420px] p-24 mx-auto">
|
||||
<h1 className="text-h3 mb-12">Deposit</h1>
|
||||
<DepositContainer assetId={assetId} />
|
||||
<DepositContainer />
|
||||
</div>
|
||||
</Web3Container>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import merge from 'lodash/merge';
|
||||
import type { TransactionResponse } from '@vegaprotocol/vegawallet-service-api-client';
|
||||
import type { TransactionResponse } from '@vegaprotocol/wallet';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
declare global {
|
||||
@@ -17,7 +17,10 @@ export function addMockVegaWalletCommands() {
|
||||
'mockVegaCommandSync',
|
||||
(override?: PartialDeep<TransactionResponse>) => {
|
||||
const defaultTransactionResponse = {
|
||||
txId: 'tx-id',
|
||||
txHash: 'tx-hash',
|
||||
sentAt: new Date().toISOString(),
|
||||
receivedAt: new Date().toISOString(),
|
||||
tx: {
|
||||
input_data:
|
||||
'CPe6vpiqsPqxDBDC1w7KPkoKQGE4Y2M0NjUwMjhiMGY4OTM4YTYzZTEzNDViYzM2ODc3ZWRmODg4MjNmOWU0ZmI4ZDRlN2VkMmFlMzAwNzA3ZTMYASABKAM4Ag==',
|
||||
@@ -28,7 +31,7 @@ export function addMockVegaWalletCommands() {
|
||||
version: 1,
|
||||
},
|
||||
From: {
|
||||
PubKey: Cypress.env('vegaPublicKey'),
|
||||
PubKey: Cypress.env('VEGA_PUBLIC_KEY'),
|
||||
},
|
||||
version: 2,
|
||||
pow: {
|
||||
|
||||
@@ -44,7 +44,7 @@ export const DealTicketManager = ({
|
||||
);
|
||||
};
|
||||
|
||||
const getDialogTitle = (status?: OrderStatus): string | undefined => {
|
||||
export const getDialogTitle = (status?: OrderStatus): string | undefined => {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
@@ -63,7 +63,7 @@ const getDialogTitle = (status?: OrderStatus): string | undefined => {
|
||||
}
|
||||
};
|
||||
|
||||
const getDialogIntent = (status?: OrderStatus): Intent | undefined => {
|
||||
export const getDialogIntent = (status?: OrderStatus): Intent | undefined => {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ const getDialogIntent = (status?: OrderStatus): Intent | undefined => {
|
||||
}
|
||||
};
|
||||
|
||||
const getDialogIcon = (status?: OrderStatus): ReactNode | undefined => {
|
||||
export const getDialogIcon = (status?: OrderStatus): ReactNode | undefined => {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { fireEvent, render, screen, act } from '@testing-library/react';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
|
||||
const market: DealTicketQuery_market = {
|
||||
__typename: 'Market',
|
||||
@@ -59,109 +59,111 @@ function generateJsx(order?: Order) {
|
||||
);
|
||||
}
|
||||
|
||||
it('Displays ticket defaults', () => {
|
||||
render(generateJsx());
|
||||
describe('DealTicket', () => {
|
||||
it('Displays ticket defaults', () => {
|
||||
render(generateJsx());
|
||||
|
||||
// Assert defaults are used
|
||||
expect(
|
||||
screen.getByTestId(`order-type-${VegaWalletOrderType.Market}-selected`)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_BUY-selected')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_SELL-selected')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
|
||||
String(1 / Math.pow(10, market.positionDecimalPlaces))
|
||||
);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.IOC
|
||||
);
|
||||
// Assert defaults are used
|
||||
expect(
|
||||
screen.getByTestId(`order-type-${VegaWalletOrderType.Market}-selected`)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_BUY-selected')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_SELL-selected')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
|
||||
String(1 / Math.pow(10, market.positionDecimalPlaces))
|
||||
);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
// Assert last price is shown
|
||||
expect(screen.getByTestId('last-price')).toHaveTextContent(
|
||||
// eslint-disable-next-line
|
||||
`~${addDecimal(market.depth.lastTrade!.price, market.decimalPlaces)} ${
|
||||
market.tradableInstrument.instrument.product.quoteName
|
||||
}`
|
||||
);
|
||||
});
|
||||
// Assert last price is shown
|
||||
expect(screen.getByTestId('last-price')).toHaveTextContent(
|
||||
// eslint-disable-next-line
|
||||
`~${addDecimal(market.depth.lastTrade!.price, market.decimalPlaces)} ${
|
||||
market.tradableInstrument.instrument.product.quoteName
|
||||
}`
|
||||
);
|
||||
});
|
||||
|
||||
it('Can edit deal ticket', async () => {
|
||||
render(generateJsx());
|
||||
it('Can edit deal ticket', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
// BUY is selected by default
|
||||
screen.getByTestId('order-side-SIDE_BUY-selected');
|
||||
// BUY is selected by default
|
||||
screen.getByTestId('order-side-SIDE_BUY-selected');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(screen.getByTestId('order-size'), {
|
||||
target: { value: '200' },
|
||||
await act(async () => {
|
||||
fireEvent.change(screen.getByTestId('order-size'), {
|
||||
target: { value: '200' },
|
||||
});
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue('200');
|
||||
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: VegaWalletOrderTimeInForce.IOC },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
// Switch to limit order
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
|
||||
|
||||
// Assert price input shown with default value
|
||||
expect(screen.getByTestId('order-price')).toHaveDisplayValue('0');
|
||||
|
||||
// Check all TIF options shown
|
||||
expect(screen.getByTestId('order-tif').children).toHaveLength(
|
||||
Object.keys(VegaWalletOrderTimeInForce).length
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue('200');
|
||||
it('Handles TIF select box dependent on order type', () => {
|
||||
render(generateJsx());
|
||||
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: VegaWalletOrderTimeInForce.IOC },
|
||||
// Check only IOC and
|
||||
expect(
|
||||
Array.from(screen.getByTestId('order-tif').children).map(
|
||||
(o) => o.textContent
|
||||
)
|
||||
).toEqual(['Immediate or Cancel (IOC)', 'Fill or Kill (FOK)']);
|
||||
|
||||
// Switch to limit order and check all TIF options shown
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
|
||||
expect(screen.getByTestId('order-tif').children).toHaveLength(
|
||||
Object.keys(VegaWalletOrderTimeInForce).length
|
||||
);
|
||||
|
||||
// Change to GTC
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: VegaWalletOrderTimeInForce.GTC },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.GTC
|
||||
);
|
||||
|
||||
// Switch back to market order and TIF should now be IOC
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_MARKET'));
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
// Switch tif to FOK
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: VegaWalletOrderTimeInForce.FOK },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.FOK
|
||||
);
|
||||
|
||||
// Change back to limit and check we are still on FOK
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.FOK
|
||||
);
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
// Switch to limit order
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
|
||||
|
||||
// Assert price input shown with default value
|
||||
expect(screen.getByTestId('order-price')).toHaveDisplayValue('0');
|
||||
|
||||
// Check all TIF options shown
|
||||
expect(screen.getByTestId('order-tif').children).toHaveLength(
|
||||
Object.keys(VegaWalletOrderTimeInForce).length
|
||||
);
|
||||
});
|
||||
|
||||
it('Handles TIF select box dependent on order type', () => {
|
||||
render(generateJsx());
|
||||
|
||||
// Check only IOC and
|
||||
expect(
|
||||
Array.from(screen.getByTestId('order-tif').children).map(
|
||||
(o) => o.textContent
|
||||
)
|
||||
).toEqual(['IOC', 'FOK']);
|
||||
|
||||
// Switch to limit order and check all TIF options shown
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
|
||||
expect(screen.getByTestId('order-tif').children).toHaveLength(
|
||||
Object.keys(VegaWalletOrderTimeInForce).length
|
||||
);
|
||||
|
||||
// Change to GTC
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: VegaWalletOrderTimeInForce.GTC },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.GTC
|
||||
);
|
||||
|
||||
// Switch back to market order and TIF should now be IOC
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_MARKET'));
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
// Switch tif to FOK
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: VegaWalletOrderTimeInForce.FOK },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.FOK
|
||||
);
|
||||
|
||||
// Change back to limit and check we are still on FOK
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.FOK
|
||||
);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,26 @@ interface TimeInForceSelectorProps {
|
||||
onSelect: (tif: VegaWalletOrderTimeInForce) => void;
|
||||
}
|
||||
|
||||
// More detail in https://docs.vega.xyz/docs/mainnet/graphql/enums/order-time-in-force
|
||||
export const timeInForceLabel = (tif: string) => {
|
||||
switch (tif) {
|
||||
case VegaWalletOrderTimeInForce.GTC:
|
||||
return t(`Good 'til Cancelled`);
|
||||
case VegaWalletOrderTimeInForce.IOC:
|
||||
return t('Immediate or Cancel');
|
||||
case VegaWalletOrderTimeInForce.FOK:
|
||||
return t('Fill or Kill');
|
||||
case VegaWalletOrderTimeInForce.GTT:
|
||||
return t(`Good 'til Time`);
|
||||
case VegaWalletOrderTimeInForce.GFN:
|
||||
return t('Good for Normal');
|
||||
case VegaWalletOrderTimeInForce.GFA:
|
||||
return t('Good for Auction');
|
||||
default:
|
||||
return t(tif);
|
||||
}
|
||||
};
|
||||
|
||||
export const TimeInForceSelector = ({
|
||||
value,
|
||||
orderType,
|
||||
@@ -37,7 +57,7 @@ export const TimeInForceSelector = ({
|
||||
{options.map(([key, value]) => {
|
||||
return (
|
||||
<option key={key} value={value}>
|
||||
{key}
|
||||
{`${timeInForceLabel(value)} (${key})`}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -37,10 +37,8 @@ beforeEach(() => {
|
||||
submitApprove: jest.fn(),
|
||||
submitDeposit: jest.fn(),
|
||||
requestFaucet: jest.fn(),
|
||||
limits: {
|
||||
max: new BigNumber(20),
|
||||
deposited: new BigNumber(10),
|
||||
},
|
||||
max: new BigNumber(20),
|
||||
deposited: new BigNumber(10),
|
||||
allowance: new BigNumber(30),
|
||||
isFaucetable: true,
|
||||
};
|
||||
@@ -134,7 +132,8 @@ describe('Deposit form', () => {
|
||||
<DepositForm
|
||||
{...props}
|
||||
balance={new BigNumber(100)}
|
||||
limits={{ max: new BigNumber(100), deposited: new BigNumber(10) }}
|
||||
max={new BigNumber(100)}
|
||||
deposited={new BigNumber(10)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -213,18 +212,16 @@ describe('Deposit form', () => {
|
||||
const mockUseWeb3React = useWeb3React as jest.Mock;
|
||||
mockUseWeb3React.mockReturnValue({ account });
|
||||
|
||||
const limits = {
|
||||
max: new BigNumber(20),
|
||||
deposited: new BigNumber(10),
|
||||
};
|
||||
const balance = new BigNumber(50);
|
||||
|
||||
const max = new BigNumber(20);
|
||||
const deposited = new BigNumber(10);
|
||||
render(
|
||||
<DepositForm
|
||||
{...props}
|
||||
allowance={new BigNumber(100)}
|
||||
balance={balance}
|
||||
limits={limits}
|
||||
max={max}
|
||||
deposited={deposited}
|
||||
selectedAsset={asset}
|
||||
/>
|
||||
);
|
||||
@@ -237,13 +234,13 @@ describe('Deposit form', () => {
|
||||
expect(
|
||||
screen.getByText('Maximum total deposit amount', { selector: 'th' })
|
||||
.nextElementSibling
|
||||
).toHaveTextContent(limits.max.toString());
|
||||
).toHaveTextContent(max.toString());
|
||||
expect(
|
||||
screen.getByText('Deposited', { selector: 'th' }).nextElementSibling
|
||||
).toHaveTextContent(limits.deposited.toString());
|
||||
).toHaveTextContent(deposited.toString());
|
||||
expect(
|
||||
screen.getByText('Remaining', { selector: 'th' }).nextElementSibling
|
||||
).toHaveTextContent(limits.max.minus(limits.deposited).toString());
|
||||
).toHaveTextContent(max.minus(deposited).toString());
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '8' },
|
||||
@@ -257,7 +254,7 @@ describe('Deposit form', () => {
|
||||
expect(props.submitDeposit).toHaveBeenCalledWith({
|
||||
// @ts-ignore contract address definitely defined
|
||||
assetSource: asset.source.contractAddress,
|
||||
amount: '800',
|
||||
amount: '8',
|
||||
vegaPublicKey: vegaKey,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
removeDecimal,
|
||||
ethereumAddress,
|
||||
t,
|
||||
required,
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
minSafe,
|
||||
maxSafe,
|
||||
addDecimal,
|
||||
isAssetTypeERC20,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
Button,
|
||||
@@ -21,10 +22,9 @@ import { useWeb3React } from '@web3-react/core';
|
||||
import { Web3WalletInput } from '@vegaprotocol/web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useMemo, useEffect } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { useMemo } from 'react';
|
||||
import { Controller, useForm, useWatch } from 'react-hook-form';
|
||||
import { DepositLimits } from './deposit-limits';
|
||||
import type { Asset } from './deposit-manager';
|
||||
|
||||
interface FormFields {
|
||||
asset: string;
|
||||
@@ -45,10 +45,8 @@ export interface DepositFormProps {
|
||||
vegaPublicKey: string;
|
||||
}) => void;
|
||||
requestFaucet: () => void;
|
||||
limits: {
|
||||
max: BigNumber;
|
||||
deposited: BigNumber;
|
||||
} | null;
|
||||
max: BigNumber | undefined;
|
||||
deposited: BigNumber | undefined;
|
||||
allowance: BigNumber | undefined;
|
||||
isFaucetable?: boolean;
|
||||
}
|
||||
@@ -58,10 +56,11 @@ export const DepositForm = ({
|
||||
selectedAsset,
|
||||
onSelectAsset,
|
||||
balance,
|
||||
max,
|
||||
deposited,
|
||||
submitApprove,
|
||||
submitDeposit,
|
||||
requestFaucet,
|
||||
limits,
|
||||
allowance,
|
||||
isFaucetable,
|
||||
}: DepositFormProps) => {
|
||||
@@ -89,15 +88,14 @@ export const DepositForm = ({
|
||||
|
||||
submitDeposit({
|
||||
assetSource: selectedAsset.source.contractAddress,
|
||||
amount: removeDecimal(fields.amount, selectedAsset.decimals),
|
||||
amount: fields.amount,
|
||||
vegaPublicKey: fields.to,
|
||||
});
|
||||
};
|
||||
|
||||
const assetId = useWatch({ name: 'asset', control });
|
||||
const amount = useWatch({ name: 'amount', control });
|
||||
|
||||
const max = useMemo(() => {
|
||||
const maxAmount = useMemo(() => {
|
||||
const maxApproved = allowance ? allowance : new BigNumber(0);
|
||||
const maxAvailable = balance ? balance : new BigNumber(0);
|
||||
|
||||
@@ -106,8 +104,8 @@ export const DepositForm = ({
|
||||
let maxLimit = new BigNumber(Infinity);
|
||||
|
||||
// A max limit of zero indicates that there is no limit
|
||||
if (limits && limits.max.isGreaterThan(0)) {
|
||||
maxLimit = limits.max.minus(limits.deposited);
|
||||
if (max && deposited && max.isGreaterThan(0)) {
|
||||
maxLimit = max.minus(deposited);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -116,7 +114,7 @@ export const DepositForm = ({
|
||||
limit: maxLimit,
|
||||
amount: BigNumber.minimum(maxLimit, maxApproved, maxAvailable),
|
||||
};
|
||||
}, [limits, allowance, balance]);
|
||||
}, [max, deposited, allowance, balance]);
|
||||
|
||||
const min = useMemo(() => {
|
||||
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
|
||||
@@ -127,10 +125,6 @@ export const DepositForm = ({
|
||||
return minViableAmount;
|
||||
}, [selectedAsset]);
|
||||
|
||||
useEffect(() => {
|
||||
onSelectAsset(assetId);
|
||||
}, [assetId, onSelectAsset]);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onDeposit)}
|
||||
@@ -154,16 +148,28 @@ export const DepositForm = ({
|
||||
)}
|
||||
</FormGroup>
|
||||
<FormGroup label={t('Asset')} labelFor="asset" className="relative">
|
||||
<Select {...register('asset', { validate: { required } })} id="asset">
|
||||
<option value="">{t('Please select')}</option>
|
||||
{assets
|
||||
.filter((a) => a.source.__typename === 'ERC20')
|
||||
.map((a) => (
|
||||
<option key={a.id} value={a.id}>
|
||||
{a.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<Controller
|
||||
control={control}
|
||||
name="asset"
|
||||
rules={{ validate: { required } }}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
id="asset"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
onSelectAsset(e.target.value);
|
||||
}}
|
||||
>
|
||||
<option value="">{t('Please select')}</option>
|
||||
{assets.filter(isAssetTypeERC20).map((a) => (
|
||||
<option key={a.id} value={a.id}>
|
||||
{a.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
{errors.asset?.message && (
|
||||
<InputError intent="danger" className="mt-4" forInput="asset">
|
||||
{errors.asset.message}
|
||||
@@ -196,9 +202,9 @@ export const DepositForm = ({
|
||||
</UseButton>
|
||||
)}
|
||||
</FormGroup>
|
||||
{selectedAsset && limits && (
|
||||
{selectedAsset && max && deposited && (
|
||||
<div className="mb-20">
|
||||
<DepositLimits limits={limits} balance={balance} />
|
||||
<DepositLimits max={max} deposited={deposited} balance={balance} />
|
||||
</div>
|
||||
)}
|
||||
<FormGroup label={t('Amount')} labelFor="amount" className="relative">
|
||||
@@ -212,14 +218,14 @@ export const DepositForm = ({
|
||||
minSafe: (value) => minSafe(new BigNumber(min))(value),
|
||||
maxSafe: (v) => {
|
||||
const value = new BigNumber(v);
|
||||
if (value.isGreaterThan(max.available)) {
|
||||
if (value.isGreaterThan(maxAmount.available)) {
|
||||
return t('Insufficient amount in Ethereum wallet');
|
||||
} else if (value.isGreaterThan(max.limit)) {
|
||||
} else if (value.isGreaterThan(maxAmount.limit)) {
|
||||
return t('Amount is above temporary deposit limit');
|
||||
} else if (value.isGreaterThan(max.approved)) {
|
||||
} else if (value.isGreaterThan(maxAmount.approved)) {
|
||||
return t('Amount is above approved amount');
|
||||
}
|
||||
return maxSafe(max.amount)(v);
|
||||
return maxSafe(maxAmount.amount)(v);
|
||||
},
|
||||
},
|
||||
})}
|
||||
|
||||
@@ -2,28 +2,30 @@ import { t } from '@vegaprotocol/react-helpers';
|
||||
import type BigNumber from 'bignumber.js';
|
||||
|
||||
interface DepositLimitsProps {
|
||||
limits: {
|
||||
max: BigNumber;
|
||||
deposited: BigNumber;
|
||||
};
|
||||
max: BigNumber;
|
||||
deposited: BigNumber;
|
||||
balance?: BigNumber;
|
||||
}
|
||||
|
||||
export const DepositLimits = ({ limits, balance }: DepositLimitsProps) => {
|
||||
export const DepositLimits = ({
|
||||
max,
|
||||
deposited,
|
||||
balance,
|
||||
}: DepositLimitsProps) => {
|
||||
let maxLimit = '';
|
||||
if (limits.max.isEqualTo(Infinity)) {
|
||||
if (max.isEqualTo(Infinity)) {
|
||||
maxLimit = t('No limit');
|
||||
} else if (limits.max.isGreaterThan(1_000_000)) {
|
||||
} else if (max.isGreaterThan(1_000_000)) {
|
||||
maxLimit = t('1m+');
|
||||
} else {
|
||||
maxLimit = limits.max.toString();
|
||||
maxLimit = max.toString();
|
||||
}
|
||||
|
||||
let remaining = '';
|
||||
if (limits.deposited.isEqualTo(0)) {
|
||||
if (deposited.isEqualTo(0)) {
|
||||
remaining = maxLimit;
|
||||
} else {
|
||||
const amountRemaining = limits.max.minus(limits.deposited);
|
||||
const amountRemaining = max.minus(deposited);
|
||||
remaining = amountRemaining.isGreaterThan(1_000_000)
|
||||
? t('1m+')
|
||||
: amountRemaining.toString();
|
||||
@@ -44,7 +46,7 @@ export const DepositLimits = ({ limits, balance }: DepositLimitsProps) => {
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="text-left font-normal">{t('Deposited')}</th>
|
||||
<td className="text-right">{limits.deposited.toString()}</td>
|
||||
<td className="text-right">{deposited.toString()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="text-left font-normal">{t('Remaining')}</th>
|
||||
|
||||
@@ -1,121 +1,56 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { DepositForm } from './deposit-form';
|
||||
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
||||
import { useSubmitDeposit } from './use-submit-deposit';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import { useSubmitApproval } from './use-submit-approval';
|
||||
import { useGetDepositLimits } from './use-get-deposit-limits';
|
||||
import { useGetAllowance } from './use-get-allowance';
|
||||
import { useSubmitFaucet } from './use-submit-faucet';
|
||||
import { EthTxStatus, useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import { useTokenContract } from '@vegaprotocol/web3';
|
||||
import { removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface ERC20AssetSource {
|
||||
__typename: 'ERC20';
|
||||
contractAddress: string;
|
||||
}
|
||||
|
||||
interface BuiltinAssetSource {
|
||||
__typename: 'BuiltinAsset';
|
||||
}
|
||||
|
||||
type AssetSource = ERC20AssetSource | BuiltinAssetSource;
|
||||
export interface Asset {
|
||||
__typename: 'Asset';
|
||||
id: string;
|
||||
symbol: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
source: AssetSource;
|
||||
}
|
||||
import { useDepositStore } from './deposit-store';
|
||||
import { useCallback } from 'react';
|
||||
import { useDepositBalances } from './use-deposit-balances';
|
||||
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface DepositManagerProps {
|
||||
assets: Asset[];
|
||||
initialAssetId?: string;
|
||||
isFaucetable?: boolean;
|
||||
isFaucetable: boolean;
|
||||
}
|
||||
|
||||
export const DepositManager = ({
|
||||
assets,
|
||||
initialAssetId,
|
||||
isFaucetable,
|
||||
}: DepositManagerProps) => {
|
||||
const [assetId, setAssetId] = useState<string | undefined>(initialAssetId);
|
||||
|
||||
// Find the asset object from the select box
|
||||
const asset = useMemo(() => {
|
||||
const asset = assets?.find((a) => a.id === assetId);
|
||||
return asset;
|
||||
}, [assets, assetId]);
|
||||
|
||||
const { config } = useEthereumConfig();
|
||||
|
||||
const tokenContract = useTokenContract(
|
||||
asset?.source.__typename === 'ERC20'
|
||||
? asset.source.contractAddress
|
||||
: undefined,
|
||||
isFaucetable
|
||||
);
|
||||
|
||||
// Get users balance of the erc20 token selected
|
||||
const { balance, refetch: refetchBalance } = useGetBalanceOfERC20Token(
|
||||
tokenContract,
|
||||
asset?.decimals
|
||||
);
|
||||
|
||||
// Get temporary deposit limits
|
||||
const limits = useGetDepositLimits(asset);
|
||||
|
||||
// Get allowance (approved spending limit of brdige contract) for the selected asset
|
||||
const { allowance, refetch: refetchAllowance } = useGetAllowance(
|
||||
tokenContract,
|
||||
asset?.decimals
|
||||
);
|
||||
const { asset, balance, allowance, deposited, max, update } =
|
||||
useDepositStore();
|
||||
useDepositBalances(isFaucetable);
|
||||
|
||||
// Set up approve transaction
|
||||
const approve = useSubmitApproval(tokenContract);
|
||||
const approve = useSubmitApproval();
|
||||
|
||||
// Set up deposit transaction
|
||||
const deposit = useSubmitDeposit();
|
||||
|
||||
// Set up faucet transaction
|
||||
const faucet = useSubmitFaucet(tokenContract);
|
||||
const faucet = useSubmitFaucet();
|
||||
|
||||
// Update balance after confirmation event has been received
|
||||
useEffect(() => {
|
||||
if (
|
||||
faucet.transaction.status === EthTxStatus.Confirmed ||
|
||||
deposit.transaction.status === EthTxStatus.Confirmed
|
||||
) {
|
||||
refetchBalance();
|
||||
}
|
||||
}, [deposit.transaction.status, faucet.transaction.status, refetchBalance]);
|
||||
|
||||
// After an approval transaction refetch allowance
|
||||
useEffect(() => {
|
||||
if (approve.transaction.status === EthTxStatus.Confirmed) {
|
||||
refetchAllowance();
|
||||
}
|
||||
}, [approve.transaction.status, refetchAllowance]);
|
||||
const handleSelectAsset = useCallback(
|
||||
(id) => {
|
||||
const asset = assets.find((a) => a.id === id);
|
||||
if (!asset) return;
|
||||
update({ asset });
|
||||
},
|
||||
[assets, update]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DepositForm
|
||||
balance={balance}
|
||||
selectedAsset={asset}
|
||||
onSelectAsset={(id) => setAssetId(id)}
|
||||
onSelectAsset={handleSelectAsset}
|
||||
assets={sortBy(assets, 'name')}
|
||||
submitApprove={() => {
|
||||
if (!asset || !config) return;
|
||||
const amount = removeDecimal('1000000', asset.decimals);
|
||||
approve.perform(config.collateral_bridge_contract.address, amount);
|
||||
}}
|
||||
submitDeposit={(args) => {
|
||||
deposit.perform(args.assetSource, args.amount, args.vegaPublicKey);
|
||||
}}
|
||||
submitApprove={() => approve.perform()}
|
||||
submitDeposit={(args) => deposit.perform(args)}
|
||||
requestFaucet={() => faucet.perform()}
|
||||
limits={limits}
|
||||
deposited={deposited}
|
||||
max={max}
|
||||
allowance={allowance}
|
||||
isFaucetable={isFaucetable}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { SetState } from 'zustand';
|
||||
import create from 'zustand';
|
||||
|
||||
interface DepositStore {
|
||||
balance: BigNumber;
|
||||
allowance: BigNumber;
|
||||
asset: Asset | undefined;
|
||||
deposited: BigNumber;
|
||||
max: BigNumber;
|
||||
update: (state: Partial<DepositStore>) => void;
|
||||
}
|
||||
|
||||
export const useDepositStore = create((set: SetState<DepositStore>) => ({
|
||||
balance: new BigNumber(0),
|
||||
allowance: new BigNumber(0),
|
||||
deposited: new BigNumber(0),
|
||||
max: new BigNumber(0),
|
||||
asset: undefined,
|
||||
update: (updatedState) => {
|
||||
set(updatedState);
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useBridgeContract, useTokenContract } from '@vegaprotocol/web3';
|
||||
import { useEffect } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { useDepositStore } from './deposit-store';
|
||||
import { useGetAllowance } from './use-get-allowance';
|
||||
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
||||
import { useGetDepositMaximum } from './use-get-deposit-maximum';
|
||||
import { useGetDepositedAmount } from './use-get-deposited-amount';
|
||||
import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers';
|
||||
|
||||
/**
|
||||
* Hook which fetches all the balances required for despoiting
|
||||
* whenever the asset changes in the form
|
||||
*/
|
||||
export const useDepositBalances = (isFaucetable: boolean) => {
|
||||
const { asset, update } = useDepositStore();
|
||||
const tokenContract = useTokenContract(
|
||||
isAssetTypeERC20(asset) ? asset : undefined,
|
||||
isFaucetable
|
||||
);
|
||||
const bridgeContract = useBridgeContract(true);
|
||||
const getAllowance = useGetAllowance(tokenContract, asset);
|
||||
const getBalance = useGetBalanceOfERC20Token(tokenContract, asset);
|
||||
const getDepositMaximum = useGetDepositMaximum(bridgeContract, asset);
|
||||
const getDepositedAmount = useGetDepositedAmount(asset);
|
||||
|
||||
useEffect(() => {
|
||||
const getBalances = async () => {
|
||||
try {
|
||||
const [max, deposited, balance, allowance] = await Promise.all([
|
||||
getDepositMaximum(),
|
||||
getDepositedAmount(),
|
||||
getBalance(),
|
||||
getAllowance(),
|
||||
]);
|
||||
|
||||
update({
|
||||
max,
|
||||
deposited,
|
||||
balance,
|
||||
allowance,
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
};
|
||||
|
||||
if (asset) {
|
||||
getBalances();
|
||||
}
|
||||
}, [
|
||||
asset,
|
||||
update,
|
||||
getDepositMaximum,
|
||||
getDepositedAmount,
|
||||
getAllowance,
|
||||
getBalance,
|
||||
]);
|
||||
};
|
||||
@@ -1,30 +1,35 @@
|
||||
import type { Token } from '@vegaprotocol/smart-contracts';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { useCallback } from 'react';
|
||||
import { useEthereumConfig, useEthereumReadContract } from '@vegaprotocol/web3';
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const useGetAllowance = (contract: Token | null, decimals?: number) => {
|
||||
export const useGetAllowance = (
|
||||
contract: Token | null,
|
||||
asset: Asset | undefined
|
||||
) => {
|
||||
const { account } = useWeb3React();
|
||||
const { config } = useEthereumConfig();
|
||||
|
||||
const getAllowance = useCallback(() => {
|
||||
if (!contract || !account || !config) {
|
||||
const getAllowance = useCallback(async () => {
|
||||
if (!contract || !account || !config || !asset) {
|
||||
return;
|
||||
}
|
||||
return contract.allowance(
|
||||
account,
|
||||
config.collateral_bridge_contract.address
|
||||
);
|
||||
}, [contract, account, config]);
|
||||
try {
|
||||
const res = await contract.allowance(
|
||||
account,
|
||||
config.collateral_bridge_contract.address
|
||||
);
|
||||
|
||||
const { state, refetch } = useEthereumReadContract(getAllowance);
|
||||
return new BigNumber(addDecimal(res.toString(), asset.decimals));
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
return;
|
||||
}
|
||||
}, [contract, account, config, asset]);
|
||||
|
||||
const allowance =
|
||||
state.data && decimals
|
||||
? new BigNumber(addDecimal(state.data.toString(), decimals))
|
||||
: undefined;
|
||||
|
||||
return { allowance, refetch };
|
||||
return getAllowance;
|
||||
};
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
import { useEthereumReadContract } from '@vegaprotocol/web3';
|
||||
import type { Token } from '@vegaprotocol/smart-contracts';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { useCallback } from 'react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const useGetBalanceOfERC20Token = (
|
||||
contract: Token | null,
|
||||
decimals: number | undefined
|
||||
asset: Asset | undefined
|
||||
) => {
|
||||
const { account } = useWeb3React();
|
||||
|
||||
const getBalance = useCallback(() => {
|
||||
if (!contract || !account) {
|
||||
const getBalance = useCallback(async () => {
|
||||
if (!contract || !asset || !account) {
|
||||
return;
|
||||
}
|
||||
|
||||
return contract.balanceOf(account);
|
||||
}, [contract, account]);
|
||||
try {
|
||||
const res = await contract.balanceOf(account);
|
||||
return new BigNumber(addDecimal(res.toString(), asset.decimals));
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
return;
|
||||
}
|
||||
}, [contract, asset, account]);
|
||||
|
||||
const { state, refetch } = useEthereumReadContract(getBalance);
|
||||
|
||||
const balance =
|
||||
state.data && decimals
|
||||
? new BigNumber(addDecimal(state.data?.toString(), decimals))
|
||||
: undefined;
|
||||
|
||||
return { balance, refetch };
|
||||
return getBalance;
|
||||
};
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { ethers } from 'ethers';
|
||||
import type { Asset } from './deposit-manager';
|
||||
import {
|
||||
useBridgeContract,
|
||||
useEthereumConfig,
|
||||
useEthereumReadContract,
|
||||
} from '@vegaprotocol/web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
|
||||
export const useGetDepositLimits = (asset?: Asset) => {
|
||||
const { account, provider } = useWeb3React();
|
||||
const { config } = useEthereumConfig();
|
||||
const contract = useBridgeContract(true);
|
||||
const [userTotal, setUserTotal] = useState<BigNumber | null>(null);
|
||||
const getLimits = useCallback(async () => {
|
||||
if (!contract || !asset || asset.source.__typename !== 'ERC20') {
|
||||
return;
|
||||
}
|
||||
|
||||
return contract.get_deposit_maximum(asset.source.contractAddress);
|
||||
}, [asset, contract]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!provider ||
|
||||
!config ||
|
||||
!account ||
|
||||
!asset ||
|
||||
asset.source.__typename !== 'ERC20'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const abicoder = new ethers.utils.AbiCoder();
|
||||
const innerHash = ethers.utils.keccak256(
|
||||
abicoder.encode(['address', 'uint256'], [account, 4])
|
||||
);
|
||||
const storageLocation = ethers.utils.keccak256(
|
||||
abicoder.encode(
|
||||
['address', 'bytes32'],
|
||||
[asset.source.contractAddress, innerHash]
|
||||
)
|
||||
);
|
||||
(async () => {
|
||||
const res = await provider.getStorageAt(
|
||||
config.collateral_bridge_contract.address,
|
||||
storageLocation
|
||||
);
|
||||
const value = new BigNumber(res, 16).toString();
|
||||
setUserTotal(new BigNumber(addDecimal(value, asset.decimals)));
|
||||
})();
|
||||
}, [provider, config, account, asset]);
|
||||
|
||||
const {
|
||||
state: { data },
|
||||
} = useEthereumReadContract(getLimits);
|
||||
|
||||
if (!data || !userTotal || !asset) return null;
|
||||
|
||||
const max = new BigNumber(addDecimal(data.toString(), asset.decimals));
|
||||
|
||||
return {
|
||||
max: max.isEqualTo(0) ? new BigNumber(Infinity) : max,
|
||||
deposited: userTotal,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useCallback } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
CollateralBridge,
|
||||
CollateralBridgeNew,
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
|
||||
export const useGetDepositMaximum = (
|
||||
contract: CollateralBridge | CollateralBridgeNew | null,
|
||||
asset: Asset | undefined
|
||||
) => {
|
||||
const getDepositMaximum = useCallback(async () => {
|
||||
if (!contract || !asset || asset.source.__typename !== 'ERC20') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await contract.get_deposit_maximum(
|
||||
asset.source.contractAddress
|
||||
);
|
||||
const max = new BigNumber(addDecimal(res.toString(), asset.decimals));
|
||||
return max.isEqualTo(0) ? new BigNumber(Infinity) : max;
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
return;
|
||||
}
|
||||
}, [contract, asset]);
|
||||
|
||||
return getDepositMaximum;
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useCallback } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { ethers } from 'ethers';
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
|
||||
export const useGetDepositedAmount = (asset: Asset | undefined) => {
|
||||
const { account, provider } = useWeb3React();
|
||||
const { config } = useEthereumConfig();
|
||||
|
||||
// For an explaination of how this code works see here: https://gist.github.com/emilbayes/44a36f59b06b1f3edb9cf914041544ed
|
||||
const getDepositedAmount = useCallback(async () => {
|
||||
if (
|
||||
!provider ||
|
||||
!config ||
|
||||
!account ||
|
||||
!asset ||
|
||||
asset.source.__typename !== 'ERC20'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const abicoder = new ethers.utils.AbiCoder();
|
||||
const innerHash = ethers.utils.keccak256(
|
||||
abicoder.encode(['address', 'uint256'], [account, 4])
|
||||
);
|
||||
const storageLocation = ethers.utils.keccak256(
|
||||
abicoder.encode(
|
||||
['address', 'bytes32'],
|
||||
[asset.source.contractAddress, innerHash]
|
||||
)
|
||||
);
|
||||
const res = await provider.getStorageAt(
|
||||
config.collateral_bridge_contract.address,
|
||||
storageLocation
|
||||
);
|
||||
const value = new BigNumber(res, 16).toString();
|
||||
return new BigNumber(addDecimal(value, asset.decimals));
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
return;
|
||||
}
|
||||
}, [provider, asset, config, account]);
|
||||
|
||||
return getDepositedAmount;
|
||||
};
|
||||
@@ -1,10 +1,41 @@
|
||||
import { isAssetTypeERC20, removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import type { Token } from '@vegaprotocol/smart-contracts';
|
||||
import { useEthereumTransaction } from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEthereumConfig,
|
||||
useEthereumTransaction,
|
||||
useTokenContract,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { useDepositStore } from './deposit-store';
|
||||
import { useGetAllowance } from './use-get-allowance';
|
||||
|
||||
export const useSubmitApproval = (contract: Token | null) => {
|
||||
export const useSubmitApproval = () => {
|
||||
const { config } = useEthereumConfig();
|
||||
const { asset, update } = useDepositStore();
|
||||
const contract = useTokenContract(
|
||||
isAssetTypeERC20(asset) ? asset : undefined,
|
||||
true
|
||||
);
|
||||
const getAllowance = useGetAllowance(contract, asset);
|
||||
const transaction = useEthereumTransaction<Token, 'approve'>(
|
||||
contract,
|
||||
'approve'
|
||||
);
|
||||
return transaction;
|
||||
return {
|
||||
...transaction,
|
||||
perform: async () => {
|
||||
if (!asset || !config) return;
|
||||
try {
|
||||
const amount = removeDecimal('1000000', asset.decimals);
|
||||
await transaction.perform(
|
||||
config.collateral_bridge_contract.address,
|
||||
amount
|
||||
);
|
||||
const allowance = await getAllowance();
|
||||
update({ allowance });
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
import { gql, useSubscription } from '@apollo/client';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import type {
|
||||
DepositEvent,
|
||||
DepositEventVariables,
|
||||
} from './__generated__/DepositEvent';
|
||||
import { DepositStatus } from '@vegaprotocol/types';
|
||||
import { useState } from 'react';
|
||||
import { remove0x } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
isAssetTypeERC20,
|
||||
remove0x,
|
||||
removeDecimal,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
useBridgeContract,
|
||||
useEthereumConfig,
|
||||
useEthereumTransaction,
|
||||
useTokenContract,
|
||||
} from '@vegaprotocol/web3';
|
||||
import type {
|
||||
CollateralBridge,
|
||||
CollateralBridgeNew,
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import { prepend0x } from '@vegaprotocol/smart-contracts';
|
||||
import { useDepositStore } from './deposit-store';
|
||||
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
||||
|
||||
const DEPOSIT_EVENT_SUB = gql`
|
||||
subscription DepositEvent($partyId: ID!) {
|
||||
@@ -32,17 +40,24 @@ const DEPOSIT_EVENT_SUB = gql`
|
||||
`;
|
||||
|
||||
export const useSubmitDeposit = () => {
|
||||
const { asset, update } = useDepositStore();
|
||||
const { config } = useEthereumConfig();
|
||||
const contract = useBridgeContract(true);
|
||||
const bridgeContract = useBridgeContract(true);
|
||||
const tokenContract = useTokenContract(
|
||||
isAssetTypeERC20(asset) ? asset : undefined,
|
||||
true
|
||||
);
|
||||
|
||||
// Store public key from contract arguments for use in the subscription,
|
||||
// NOTE: it may be different from the users connected key
|
||||
const [partyId, setPartyId] = useState<string | null>(null);
|
||||
|
||||
const getBalance = useGetBalanceOfERC20Token(tokenContract, asset);
|
||||
|
||||
const transaction = useEthereumTransaction<
|
||||
CollateralBridgeNew | CollateralBridge,
|
||||
'deposit_asset'
|
||||
>(contract, 'deposit_asset', config?.confirmations, true);
|
||||
>(bridgeContract, 'deposit_asset', config?.confirmations, true);
|
||||
|
||||
useSubscription<DepositEvent, DepositEventVariables>(DEPOSIT_EVENT_SUB, {
|
||||
variables: { partyId: partyId ? remove0x(partyId) : '' },
|
||||
@@ -78,10 +93,22 @@ export const useSubmitDeposit = () => {
|
||||
|
||||
return {
|
||||
...transaction,
|
||||
perform: (...args: Parameters<typeof transaction.perform>) => {
|
||||
setPartyId(args[2]);
|
||||
const publicKey = prepend0x(args[2]);
|
||||
transaction.perform(args[0], args[1], publicKey);
|
||||
perform: async (args: {
|
||||
assetSource: string;
|
||||
amount: string;
|
||||
vegaPublicKey: string;
|
||||
}) => {
|
||||
if (!asset) return;
|
||||
try {
|
||||
setPartyId(args.vegaPublicKey);
|
||||
const publicKey = prepend0x(args.vegaPublicKey);
|
||||
const amount = removeDecimal(args.amount, asset.decimals);
|
||||
await transaction.perform(args.assetSource, amount, publicKey);
|
||||
const balance = await getBalance();
|
||||
update({ balance });
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,10 +1,31 @@
|
||||
import type { Token, TokenFaucetable } from '@vegaprotocol/smart-contracts';
|
||||
import { useEthereumTransaction } from '@vegaprotocol/web3';
|
||||
import type { TokenFaucetable } from '@vegaprotocol/smart-contracts';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { useEthereumTransaction, useTokenContract } from '@vegaprotocol/web3';
|
||||
import { useDepositStore } from './deposit-store';
|
||||
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
||||
import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const useSubmitFaucet = (contract: Token | TokenFaucetable | null) => {
|
||||
export const useSubmitFaucet = () => {
|
||||
const { asset, update } = useDepositStore();
|
||||
const contract = useTokenContract(
|
||||
isAssetTypeERC20(asset) ? asset : undefined,
|
||||
true
|
||||
);
|
||||
const getBalance = useGetBalanceOfERC20Token(contract, asset);
|
||||
const transaction = useEthereumTransaction<TokenFaucetable, 'faucet'>(
|
||||
contract,
|
||||
'faucet'
|
||||
);
|
||||
return transaction;
|
||||
return {
|
||||
...transaction,
|
||||
perform: async () => {
|
||||
try {
|
||||
await transaction.perform();
|
||||
const balance = await getBalance();
|
||||
update({ balance });
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -12,6 +12,8 @@ The environment variables needed to be present for any app consuming this librar
|
||||
|
||||
`NX_VEGA_URL` OR `NX_VEGA_CONFIG_URL` - either the network configuration url or a url to a node to directly connect to
|
||||
|
||||
`NX_VEGA_WALLET_URL` the default vega wallet URL
|
||||
|
||||
For examples, see Block Explorer's .env files [here](../../apps/explorer)
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user