Merge branch 'master' of github.com:vegaprotocol/frontend-monorepo
This commit is contained in:
commit
c6284a91ef
@ -96,7 +96,7 @@ To run tests locally using your own wallets you can add the following environmen
|
||||
In CI linting, formatting and also run. These checks can be seen in the [CI workflow file](.github/workflows//test.yml).
|
||||
|
||||
- To fix linting errors locally run `yarn nx lint --fix`
|
||||
- To fix formatting errors local run `yarn nx format`
|
||||
- To fix formatting errors local run `yarn nx format:write`
|
||||
- For either command you may use `--all` to run across the entire repository
|
||||
|
||||
### Further help with Nx
|
||||
|
@ -10,7 +10,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
|
@ -2,7 +2,10 @@ import { useState, useEffect, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { ApolloProvider } from '@apollo/client';
|
||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
useThemeSwitcher,
|
||||
EnvironmentProvider,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { createClient } from './lib/apollo-client';
|
||||
import { Nav } from './components/nav';
|
||||
import { Header } from './components/header';
|
||||
@ -23,27 +26,29 @@ function App() {
|
||||
const client = useMemo(() => createClient(DATA_SOURCES.dataNodeUrl), []);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<TendermintWebsocketProvider>
|
||||
<ApolloProvider client={client}>
|
||||
<div
|
||||
className={`${
|
||||
menuOpen && 'h-[100vh] overflow-hidden'
|
||||
} antialiased m-0 bg-white dark:bg-black text-black dark:text-white`}
|
||||
>
|
||||
<div className="min-h-[100vh] max-w-[1300px] grid grid-rows-[repeat(2,_auto)_1fr] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)] md:grid-cols-[300px_1fr] border-black dark:border-white lg:border-l-1 lg:border-r-1 mx-auto">
|
||||
<Header
|
||||
toggleTheme={toggleTheme}
|
||||
menuOpen={menuOpen}
|
||||
setMenuOpen={setMenuOpen}
|
||||
/>
|
||||
<Nav menuOpen={menuOpen} />
|
||||
<Main />
|
||||
<EnvironmentProvider>
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<TendermintWebsocketProvider>
|
||||
<ApolloProvider client={client}>
|
||||
<div
|
||||
className={`${
|
||||
menuOpen && 'h-[100vh] overflow-hidden'
|
||||
} antialiased m-0 bg-white dark:bg-black text-black dark:text-white`}
|
||||
>
|
||||
<div className="min-h-[100vh] max-w-[1300px] grid grid-rows-[repeat(2,_auto)_1fr] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)] md:grid-cols-[300px_1fr] border-black dark:border-white lg:border-l-1 lg:border-r-1 mx-auto">
|
||||
<Header
|
||||
toggleTheme={toggleTheme}
|
||||
menuOpen={menuOpen}
|
||||
setMenuOpen={setMenuOpen}
|
||||
/>
|
||||
<Nav menuOpen={menuOpen} />
|
||||
<Main />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ApolloProvider>
|
||||
</TendermintWebsocketProvider>
|
||||
</ThemeContext.Provider>
|
||||
</ApolloProvider>
|
||||
</TendermintWebsocketProvider>
|
||||
</ThemeContext.Provider>
|
||||
</EnvironmentProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,7 @@
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"lib": ["es5", "es6", "dom", "dom.iterable"],
|
||||
"resolveJsonModule": true
|
||||
"lib": ["es5", "es6", "dom", "dom.iterable"]
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
VegaManageDialog,
|
||||
VegaWalletProvider,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { EnvironmentProvider } from '@vegaprotocol/react-helpers';
|
||||
import { VegaWalletConnectButton } from './components/vega-wallet-connect-button';
|
||||
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
|
||||
import { Connectors } from './lib/vega-connectors';
|
||||
@ -37,51 +38,53 @@ function App() {
|
||||
}, [location]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<ApolloProvider client={client}>
|
||||
<VegaWalletProvider>
|
||||
<AppLoader>
|
||||
<div className="h-full dark:bg-black dark:text-white-60 bg-white text-black-60 grid grid-rows-[min-content,1fr]">
|
||||
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
|
||||
<DrawerToggle
|
||||
onToggle={onToggle}
|
||||
variant={DRAWER_TOGGLE_VARIANTS.OPEN}
|
||||
className="xs:py-32 xs:px-16"
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-4 ml-auto mr-8">
|
||||
<VegaWalletConnectButton
|
||||
setConnectDialog={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, connect: open }))
|
||||
}
|
||||
setManageDialog={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, manage: open }))
|
||||
}
|
||||
<EnvironmentProvider>
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<ApolloProvider client={client}>
|
||||
<VegaWalletProvider>
|
||||
<AppLoader>
|
||||
<div className="h-full dark:bg-black dark:text-white-60 bg-white text-black-60 grid grid-rows-[min-content,1fr]">
|
||||
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
|
||||
<DrawerToggle
|
||||
onToggle={onToggle}
|
||||
variant={DRAWER_TOGGLE_VARIANTS.OPEN}
|
||||
className="xs:py-32 xs:px-16"
|
||||
/>
|
||||
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
|
||||
|
||||
<div className="flex items-center gap-4 ml-auto mr-8">
|
||||
<VegaWalletConnectButton
|
||||
setConnectDialog={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, connect: open }))
|
||||
}
|
||||
setManageDialog={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, manage: open }))
|
||||
}
|
||||
/>
|
||||
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Main isMenuOpen={menuOpen} onToggle={onToggle} />
|
||||
|
||||
<VegaConnectDialog
|
||||
connectors={Connectors}
|
||||
dialogOpen={vegaWallet.connect}
|
||||
setDialogOpen={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, connect: open }))
|
||||
}
|
||||
/>
|
||||
<VegaManageDialog
|
||||
dialogOpen={vegaWallet.manage}
|
||||
setDialogOpen={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, manage: open }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Main isMenuOpen={menuOpen} onToggle={onToggle} />
|
||||
|
||||
<VegaConnectDialog
|
||||
connectors={Connectors}
|
||||
dialogOpen={vegaWallet.connect}
|
||||
setDialogOpen={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, connect: open }))
|
||||
}
|
||||
/>
|
||||
<VegaManageDialog
|
||||
dialogOpen={vegaWallet.manage}
|
||||
setDialogOpen={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, manage: open }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</AppLoader>
|
||||
</VegaWalletProvider>
|
||||
</ApolloProvider>
|
||||
</ThemeContext.Provider>
|
||||
</AppLoader>
|
||||
</VegaWalletProvider>
|
||||
</ApolloProvider>
|
||||
</ThemeContext.Provider>
|
||||
</EnvironmentProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,40 +1,54 @@
|
||||
import * as React from 'react';
|
||||
import type { FormEvent } from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import Box from '@mui/material/Box';
|
||||
import { Stepper } from '../stepper';
|
||||
import type { Order, DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
|
||||
import type { DealTicketQuery_market, Order } from '@vegaprotocol/deal-ticket';
|
||||
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ExpirySelector,
|
||||
SideSelector,
|
||||
SubmitButton,
|
||||
TimeInForceSelector,
|
||||
TypeSelector,
|
||||
useOrderState,
|
||||
getDefaultOrder,
|
||||
useOrderValidation,
|
||||
useOrderSubmit,
|
||||
DealTicketLimitForm,
|
||||
DealTicketMarketForm,
|
||||
DealTicketAmount,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import {
|
||||
OrderSide,
|
||||
OrderTimeInForce,
|
||||
OrderType,
|
||||
VegaTxStatus,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface DealTicketMarketProps {
|
||||
market: DealTicketQuery_market;
|
||||
}
|
||||
|
||||
const DEFAULT_ORDER: Order = {
|
||||
type: OrderType.Market,
|
||||
side: OrderSide.Buy,
|
||||
size: '1',
|
||||
timeInForce: OrderTimeInForce.IOC,
|
||||
};
|
||||
|
||||
export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
const [order, updateOrder] = useOrderState(DEFAULT_ORDER);
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<Order>({
|
||||
mode: 'onChange',
|
||||
defaultValues: getDefaultOrder(market),
|
||||
});
|
||||
|
||||
const step = toDecimal(market.positionDecimalPlaces);
|
||||
const orderType = watch('type');
|
||||
const orderTimeInForce = watch('timeInForce');
|
||||
|
||||
const invalidText = useOrderValidation({
|
||||
step,
|
||||
market,
|
||||
orderType,
|
||||
orderTimeInForce,
|
||||
fieldErrors: errors,
|
||||
});
|
||||
|
||||
const { submit, transaction } = useOrderSubmit(market);
|
||||
|
||||
const transactionStatus =
|
||||
@ -43,39 +57,14 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
? 'pending'
|
||||
: 'default';
|
||||
|
||||
let ticket = null;
|
||||
|
||||
if (order.type === OrderType.Market) {
|
||||
ticket = (
|
||||
<DealTicketMarketForm
|
||||
size={order.size}
|
||||
onSizeChange={(size) => updateOrder({ size })}
|
||||
price={
|
||||
market.depth.lastTrade
|
||||
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
||||
: undefined
|
||||
}
|
||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||
/>
|
||||
);
|
||||
} else if (order.type === OrderType.Limit) {
|
||||
ticket = (
|
||||
<DealTicketLimitForm
|
||||
price={order.price}
|
||||
size={order.size}
|
||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||
onSizeChange={(size) => updateOrder({ size })}
|
||||
onPriceChange={(price) => updateOrder({ price })}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
throw new Error('Invalid ticket type');
|
||||
}
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>): Promise<void> => {
|
||||
e.preventDefault();
|
||||
return submit(order);
|
||||
};
|
||||
const onSubmit = React.useCallback(
|
||||
(order: Order) => {
|
||||
if (transactionStatus !== 'pending') {
|
||||
submit(order);
|
||||
}
|
||||
},
|
||||
[transactionStatus, submit]
|
||||
);
|
||||
|
||||
const steps = [
|
||||
{
|
||||
@ -87,9 +76,12 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
label: 'Select Order Type',
|
||||
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
|
||||
component: (
|
||||
<TypeSelector
|
||||
order={order}
|
||||
onSelect={(type) => updateOrder({ type })}
|
||||
<Controller
|
||||
name="type"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TypeSelector value={field.value} onSelect={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -97,9 +89,12 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
label: 'Select Market Position',
|
||||
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
|
||||
component: (
|
||||
<SideSelector
|
||||
order={order}
|
||||
onSelect={(side) => updateOrder({ side })}
|
||||
<Controller
|
||||
name="side"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SideSelector value={field.value} onSelect={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -107,27 +102,49 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
label: 'Select Order Size',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
||||
component: ticket,
|
||||
component: (
|
||||
<DealTicketAmount
|
||||
orderType={orderType}
|
||||
step={0.02}
|
||||
register={register}
|
||||
price={
|
||||
market.depth.lastTrade
|
||||
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
||||
: undefined
|
||||
}
|
||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Select Time In Force',
|
||||
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
|
||||
component: (
|
||||
<>
|
||||
<TimeInForceSelector
|
||||
order={order}
|
||||
onSelect={(timeInForce) => updateOrder({ timeInForce })}
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TimeInForceSelector
|
||||
value={field.value}
|
||||
orderType={orderType}
|
||||
onSelect={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{order.timeInForce === OrderTimeInForce.GTT && (
|
||||
<ExpirySelector
|
||||
order={order}
|
||||
onSelect={(date) => {
|
||||
if (date) {
|
||||
updateOrder({ expiration: date });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{orderType === OrderType.Limit &&
|
||||
orderTimeInForce === OrderTimeInForce.GTT && (
|
||||
<Controller
|
||||
name="expiration"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<ExpirySelector
|
||||
value={field.value}
|
||||
onSelect={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
@ -136,11 +153,22 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
|
||||
component: (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<SubmitButton
|
||||
transactionStatus={transactionStatus}
|
||||
market={market}
|
||||
order={order}
|
||||
/>
|
||||
{invalidText && (
|
||||
<InputError className="mb-8" data-testid="dealticket-error-message">
|
||||
{invalidText}
|
||||
</InputError>
|
||||
)}
|
||||
<Button
|
||||
className="w-full mb-8"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={transactionStatus === 'pending' || !!invalidText}
|
||||
data-testid="place-order"
|
||||
>
|
||||
{transactionStatus === 'pending'
|
||||
? t('Pending...')
|
||||
: t('Place order')}
|
||||
</Button>
|
||||
</Box>
|
||||
),
|
||||
disabled: true,
|
||||
@ -148,7 +176,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
];
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="px-4 py-8">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="px-4 py-8">
|
||||
<Stepper steps={steps} />
|
||||
</form>
|
||||
);
|
||||
|
@ -8,7 +8,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
|
@ -488,7 +488,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "97499.58",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "71824.744464790495556796",
|
||||
"locked_amount": "71359.360754532949904994",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "97499.58",
|
||||
@ -521,7 +521,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "135173.4239508",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "98172.02565853694305378077588",
|
||||
"locked_amount": "97535.92647176062142134244004",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "135173.4239508",
|
||||
@ -554,7 +554,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "32499.86",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "30215.429649344600035924",
|
||||
"locked_amount": "30019.65075918605161088",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "32499.86",
|
||||
@ -587,7 +587,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "10833.29",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "9834.8291472726030138",
|
||||
"locked_amount": "9771.105018319376583514",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "10833.29",
|
||||
@ -675,7 +675,7 @@
|
||||
"tranche_end": "2022-11-01T00:00:00.000Z",
|
||||
"total_added": "22500",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "18790.38156702898575",
|
||||
"locked_amount": "18545.82059556159525",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "15000",
|
||||
@ -761,7 +761,7 @@
|
||||
"tranche_end": "2023-06-02T00:00:00.000Z",
|
||||
"total_added": "1939928.38",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "1939928.38",
|
||||
"locked_amount": "1938140.454506446441067284",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1852091.69",
|
||||
@ -814,7 +814,7 @@
|
||||
"tranche_end": "2022-06-02T00:00:00.000Z",
|
||||
"total_added": "1121510.3963",
|
||||
"total_removed": "922703.8062907670614",
|
||||
"locked_amount": "10251.1276633719903559980853",
|
||||
"locked_amount": "0",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "7187.236",
|
||||
@ -1777,7 +1777,7 @@
|
||||
"tranche_end": "2022-09-30T00:00:00.000Z",
|
||||
"total_added": "60916.66666633337",
|
||||
"total_removed": "17568.575895506846757997",
|
||||
"locked_amount": "19003.432880158768918954969915924",
|
||||
"locked_amount": "18691.0451412597139137896684208311",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "2833.333333",
|
||||
@ -3206,8 +3206,8 @@
|
||||
"tranche_id": 10,
|
||||
"tranche_start": "2021-07-15T23:37:11.000Z",
|
||||
"tranche_end": "2021-07-15T23:37:11.000Z",
|
||||
"total_added": "3425768.150000000000000001",
|
||||
"total_removed": "3417578.640000000000000001",
|
||||
"total_added": "3475768.150000000000000001",
|
||||
"total_removed": "3467578.640000000000000001",
|
||||
"locked_amount": "0",
|
||||
"deposits": [
|
||||
{
|
||||
@ -3230,6 +3230,11 @@
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"tx": "0x353c9a2464262be10f3a07acaf2be06dd90749c60da1af070ff053d2dcc2f8a2"
|
||||
},
|
||||
{
|
||||
"amount": "50000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"tx": "0xf96efc6a81c0e18f7d760f060797dbb213b4b472855fc8abd578e0be183d1d6f"
|
||||
},
|
||||
{
|
||||
"amount": "1",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
@ -3617,6 +3622,11 @@
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"tx": "0x499106f146fbaa9dd63bfbd3e6452dcdd5d845371c81e64962587c0b8bb43d10"
|
||||
},
|
||||
{
|
||||
"amount": "50000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"tx": "0x3a7c5f9a15ec899a68ad41701bfafd6f3c8ce55954dbeaeb343680378e6c00c0"
|
||||
},
|
||||
{
|
||||
"amount": "1",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
@ -3961,6 +3971,12 @@
|
||||
"tranche_id": 10,
|
||||
"tx": "0x353c9a2464262be10f3a07acaf2be06dd90749c60da1af070ff053d2dcc2f8a2"
|
||||
},
|
||||
{
|
||||
"amount": "50000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"tranche_id": 10,
|
||||
"tx": "0xf96efc6a81c0e18f7d760f060797dbb213b4b472855fc8abd578e0be183d1d6f"
|
||||
},
|
||||
{
|
||||
"amount": "1",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
@ -3999,6 +4015,12 @@
|
||||
"tranche_id": 10,
|
||||
"tx": "0x499106f146fbaa9dd63bfbd3e6452dcdd5d845371c81e64962587c0b8bb43d10"
|
||||
},
|
||||
{
|
||||
"amount": "50000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"tranche_id": 10,
|
||||
"tx": "0x3a7c5f9a15ec899a68ad41701bfafd6f3c8ce55954dbeaeb343680378e6c00c0"
|
||||
},
|
||||
{
|
||||
"amount": "1",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
@ -4018,8 +4040,8 @@
|
||||
"tx": "0xac16a4ce688d40a482a59914d68c3a676592f8804ee8f0781b66a4ba5ccfbdfc"
|
||||
}
|
||||
],
|
||||
"total_tokens": "279451",
|
||||
"withdrawn_tokens": "279451",
|
||||
"total_tokens": "329451",
|
||||
"withdrawn_tokens": "329451",
|
||||
"remaining_tokens": "0"
|
||||
},
|
||||
{
|
||||
@ -5017,9 +5039,9 @@
|
||||
"tranche_id": 11,
|
||||
"tranche_start": "2021-09-03T00:00:00.000Z",
|
||||
"tranche_end": "2022-09-03T00:00:00.000Z",
|
||||
"total_added": "14473.000000000000000003",
|
||||
"total_added": "15073.000000000000000003",
|
||||
"total_removed": "2897.73152892253",
|
||||
"locked_amount": "3753.6048590816850461007780566971080671",
|
||||
"locked_amount": "3826.6258515664639083007616186263318113",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "10",
|
||||
@ -5606,6 +5628,51 @@
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tx": "0xb5ce5dbf53dc455d902f8be045d73604ac520aad1819209281568c36f2a67fb8"
|
||||
},
|
||||
{
|
||||
"amount": "35",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tx": "0x29c7f3a1d2cb3d38c1a71ff16ae5a49ec04396efcf3b5a5af5a7b7644e2790b9"
|
||||
},
|
||||
{
|
||||
"amount": "300",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tx": "0x34f9438d653258c7f2a13d5b9895de67e071db8f4e6f4f871b41700537b78296"
|
||||
},
|
||||
{
|
||||
"amount": "80",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tx": "0x4841c1749a749f260071c6083a57fd4ac81a1f89c6ae5fdb3923c7dc2ccea784"
|
||||
},
|
||||
{
|
||||
"amount": "10",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tx": "0xb3effa4c0d44cc0618748d64a49772033316fd416b4543fef7e64a4995123234"
|
||||
},
|
||||
{
|
||||
"amount": "80",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tx": "0x7ae3f7b39317bbe75034811f513f27a895cdacc645d6ea528c1230bb7713ddef"
|
||||
},
|
||||
{
|
||||
"amount": "30",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tx": "0x9af4905761376f50e57da3a5c5fc976d09cf171d3e160dcf16f833971d5c4ef4"
|
||||
},
|
||||
{
|
||||
"amount": "25",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tx": "0x7a19b511ce85a24a6a67526c4037abe394bd58552ef66efdcc5c1473d7ca2db5"
|
||||
},
|
||||
{
|
||||
"amount": "30",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tx": "0xf4c9400f35f2c0791980f2a40e944225cdeb90cb61336e145f9fc49136134591"
|
||||
},
|
||||
{
|
||||
"amount": "10",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tx": "0x32dc4ad84f622b72d89d01b2411c999ded247ed19821418691957d9f90da180a"
|
||||
},
|
||||
{
|
||||
"amount": "25",
|
||||
"user": "0xc6A53Dbb3423555C990Fc842A28CF58edC35DC73",
|
||||
@ -8929,12 +8996,66 @@
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0xb5ce5dbf53dc455d902f8be045d73604ac520aad1819209281568c36f2a67fb8"
|
||||
},
|
||||
{
|
||||
"amount": "35",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0x29c7f3a1d2cb3d38c1a71ff16ae5a49ec04396efcf3b5a5af5a7b7644e2790b9"
|
||||
},
|
||||
{
|
||||
"amount": "300",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0x34f9438d653258c7f2a13d5b9895de67e071db8f4e6f4f871b41700537b78296"
|
||||
},
|
||||
{
|
||||
"amount": "80",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0x4841c1749a749f260071c6083a57fd4ac81a1f89c6ae5fdb3923c7dc2ccea784"
|
||||
},
|
||||
{
|
||||
"amount": "10",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0xb3effa4c0d44cc0618748d64a49772033316fd416b4543fef7e64a4995123234"
|
||||
},
|
||||
{
|
||||
"amount": "80",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0x7ae3f7b39317bbe75034811f513f27a895cdacc645d6ea528c1230bb7713ddef"
|
||||
},
|
||||
{
|
||||
"amount": "30",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0x9af4905761376f50e57da3a5c5fc976d09cf171d3e160dcf16f833971d5c4ef4"
|
||||
},
|
||||
{
|
||||
"amount": "25",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0x7a19b511ce85a24a6a67526c4037abe394bd58552ef66efdcc5c1473d7ca2db5"
|
||||
},
|
||||
{
|
||||
"amount": "30",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0xf4c9400f35f2c0791980f2a40e944225cdeb90cb61336e145f9fc49136134591"
|
||||
},
|
||||
{
|
||||
"amount": "10",
|
||||
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0x32dc4ad84f622b72d89d01b2411c999ded247ed19821418691957d9f90da180a"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"total_tokens": "10",
|
||||
"total_tokens": "610",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "10"
|
||||
"remaining_tokens": "610"
|
||||
},
|
||||
{
|
||||
"address": "0xc6A53Dbb3423555C990Fc842A28CF58edC35DC73",
|
||||
@ -12786,7 +12907,7 @@
|
||||
"tranche_end": "2023-06-05T00:00:00.000Z",
|
||||
"total_added": "3732368.4671",
|
||||
"total_removed": "74162.9780761646031",
|
||||
"locked_amount": "3019082.347375474584450246",
|
||||
"locked_amount": "3002748.41446940756547250008",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1998.95815",
|
||||
@ -15450,8 +15571,8 @@
|
||||
"tranche_start": "2021-11-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-05-05T00:00:00.000Z",
|
||||
"total_added": "14597706.0446472999",
|
||||
"total_removed": "1562375.666880815858067983",
|
||||
"locked_amount": "9054416.05714107182492323023185274",
|
||||
"total_removed": "1563001.535509480008878233",
|
||||
"locked_amount": "9000945.53475187470322777308704175",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "129284.449",
|
||||
@ -15790,6 +15911,11 @@
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tx": "0x47879bd74b0adbf7cbfd0ea1e0175bcc202ac5c224dacf94d7a4ef6a2f1de9a0"
|
||||
},
|
||||
{
|
||||
"amount": "625.86862866415081025",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tx": "0x3f1666dc9c401996a37b4543b7945d14f6d66561cd1d0a84caa30b92fc55408e"
|
||||
},
|
||||
{
|
||||
"amount": "58127.81687116447134108",
|
||||
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
|
||||
@ -17195,6 +17321,12 @@
|
||||
"tranche_id": 3,
|
||||
"tx": "0x47879bd74b0adbf7cbfd0ea1e0175bcc202ac5c224dacf94d7a4ef6a2f1de9a0"
|
||||
},
|
||||
{
|
||||
"amount": "625.86862866415081025",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tranche_id": 3,
|
||||
"tx": "0x3f1666dc9c401996a37b4543b7945d14f6d66561cd1d0a84caa30b92fc55408e"
|
||||
},
|
||||
{
|
||||
"amount": "673.86655774325828525",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
@ -18217,8 +18349,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "359123.469575",
|
||||
"withdrawn_tokens": "136362.15277444105946275",
|
||||
"remaining_tokens": "222761.31680055894053725"
|
||||
"withdrawn_tokens": "136988.021403105210273",
|
||||
"remaining_tokens": "222135.448171894789727"
|
||||
},
|
||||
{
|
||||
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
|
||||
@ -19189,8 +19321,8 @@
|
||||
"tranche_start": "2021-10-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "5778205.3912159303",
|
||||
"total_removed": "1270541.085579567787916742",
|
||||
"locked_amount": "3260551.14128864886179085042406401",
|
||||
"total_removed": "1292362.811321297942666742",
|
||||
"locked_amount": "3239424.61450565009103150955191263",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "552496.6455",
|
||||
@ -19349,6 +19481,11 @@
|
||||
"user": "0x6ae83EAB68b7112BaD5AfD72d6B24546AbFF137D",
|
||||
"tx": "0xb6a0d0df674d88b02c3cbfcd26df1c379545ea24ff8fa5a0f35810e7606a687d"
|
||||
},
|
||||
{
|
||||
"amount": "21821.72574173015475",
|
||||
"user": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3",
|
||||
"tx": "0x8e9f65bfea45a61e4f20108bc0ad1ce2bdeba8e355c7b1d4e024a3ffd73065a4"
|
||||
},
|
||||
{
|
||||
"amount": "7013.412182841867109",
|
||||
"user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75",
|
||||
@ -19844,6 +19981,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "21821.72574173015475",
|
||||
"user": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3",
|
||||
"tranche_id": 4,
|
||||
"tx": "0x8e9f65bfea45a61e4f20108bc0ad1ce2bdeba8e355c7b1d4e024a3ffd73065a4"
|
||||
},
|
||||
{
|
||||
"amount": "74521.210878260278004",
|
||||
"user": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3",
|
||||
@ -19852,8 +19995,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "220999.0582",
|
||||
"withdrawn_tokens": "74521.210878260278004",
|
||||
"remaining_tokens": "146477.847321739721996"
|
||||
"withdrawn_tokens": "96342.936619990432754",
|
||||
"remaining_tokens": "124656.121580009567246"
|
||||
},
|
||||
{
|
||||
"address": "0x6ae83EAB68b7112BaD5AfD72d6B24546AbFF137D",
|
||||
@ -20221,10 +20364,15 @@
|
||||
"tranche_id": 5,
|
||||
"tranche_start": "2022-06-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-06-05T00:00:00.000Z",
|
||||
"total_added": "469355.6199999996",
|
||||
"total_added": "472355.6199999996",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "469355.6199999996",
|
||||
"locked_amount": "472355.6199999996",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "3000",
|
||||
"user": "0xD18ffAa4a1d16f9eD9d3BE4078738Eeda3f160FD",
|
||||
"tx": "0xca9ae1a4cdf8f152cc4f139b62ed65a266c39fc6d0180dc1237feee0d46eb0d3"
|
||||
},
|
||||
{
|
||||
"amount": "22500",
|
||||
"user": "0x3b208631B70e4a98bBee864effc7908501305c1f",
|
||||
@ -26838,6 +26986,21 @@
|
||||
],
|
||||
"withdrawals": [],
|
||||
"users": [
|
||||
{
|
||||
"address": "0xD18ffAa4a1d16f9eD9d3BE4078738Eeda3f160FD",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "3000",
|
||||
"user": "0xD18ffAa4a1d16f9eD9d3BE4078738Eeda3f160FD",
|
||||
"tranche_id": 5,
|
||||
"tx": "0xca9ae1a4cdf8f152cc4f139b62ed65a266c39fc6d0180dc1237feee0d46eb0d3"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"total_tokens": "3000",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "3000"
|
||||
},
|
||||
{
|
||||
"address": "0x3b208631B70e4a98bBee864effc7908501305c1f",
|
||||
"deposits": [
|
||||
@ -45856,8 +46019,8 @@
|
||||
"tranche_start": "2021-12-05T00:00:00.000Z",
|
||||
"tranche_end": "2022-06-05T00:00:00.000Z",
|
||||
"total_added": "171288.42",
|
||||
"total_removed": "16804.8745535697803",
|
||||
"locked_amount": "4389.09147635835649595952",
|
||||
"total_removed": "17067.9263679026803",
|
||||
"locked_amount": "2506.83382533958609789446",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "250",
|
||||
@ -50161,6 +50324,21 @@
|
||||
"user": "0x4f829afFDe62De9a7E819c475D20556EdC85971f",
|
||||
"tx": "0x5ec25c61dab464ed2d1caeb79d6172e60b0965bed21d9293c1c0424e83268118"
|
||||
},
|
||||
{
|
||||
"amount": "185.58552414",
|
||||
"user": "0xcB7C51a63110F2669D33cadf593E838e7EdD8007",
|
||||
"tx": "0x0d774d5fcf278d1b33eb7942a6c350b2303693d90d67e733132de00d97589a78"
|
||||
},
|
||||
{
|
||||
"amount": "39.1320029504",
|
||||
"user": "0x2e745Bf52Bd236dA8C4944d82233da08BAE49DA1",
|
||||
"tx": "0x1a5ccef5c59ceb3f82e1d4121ef16b29590e7f07139f6a34690c3e3a85d139d0"
|
||||
},
|
||||
{
|
||||
"amount": "38.3342872425",
|
||||
"user": "0x27049a430Df8b89Ae4f899b0383B0C876F9cAcEb",
|
||||
"tx": "0x2dadd46cff92cdcb974ba556776f69b136a526e05391e07618503526ef3667e0"
|
||||
},
|
||||
{
|
||||
"amount": "252.08931115",
|
||||
"user": "0xE245503f8a33eBDDD718915132869C9Ba9df37A8",
|
||||
@ -51319,10 +51497,17 @@
|
||||
"tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "39.1320029504",
|
||||
"user": "0x2e745Bf52Bd236dA8C4944d82233da08BAE49DA1",
|
||||
"tranche_id": 6,
|
||||
"tx": "0x1a5ccef5c59ceb3f82e1d4121ef16b29590e7f07139f6a34690c3e3a85d139d0"
|
||||
}
|
||||
],
|
||||
"total_tokens": "40",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "40"
|
||||
"withdrawn_tokens": "39.1320029504",
|
||||
"remaining_tokens": "0.8679970496"
|
||||
},
|
||||
{
|
||||
"address": "0x505180B5570Ef6E607aA42DB4b67B5E7a8253C5c",
|
||||
@ -58611,6 +58796,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "38.3342872425",
|
||||
"user": "0x27049a430Df8b89Ae4f899b0383B0C876F9cAcEb",
|
||||
"tranche_id": 6,
|
||||
"tx": "0x2dadd46cff92cdcb974ba556776f69b136a526e05391e07618503526ef3667e0"
|
||||
},
|
||||
{
|
||||
"amount": "41.3456291975",
|
||||
"user": "0x27049a430Df8b89Ae4f899b0383B0C876F9cAcEb",
|
||||
@ -58643,8 +58834,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "250",
|
||||
"withdrawn_tokens": "207.9011656725",
|
||||
"remaining_tokens": "42.0988343275"
|
||||
"withdrawn_tokens": "246.235452915",
|
||||
"remaining_tokens": "3.764547085"
|
||||
},
|
||||
{
|
||||
"address": "0x42bC480928828C57c39649A7D10e41227b6d5E4F",
|
||||
@ -63624,6 +63815,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "185.58552414",
|
||||
"user": "0xcB7C51a63110F2669D33cadf593E838e7EdD8007",
|
||||
"tranche_id": 6,
|
||||
"tx": "0x0d774d5fcf278d1b33eb7942a6c350b2303693d90d67e733132de00d97589a78"
|
||||
},
|
||||
{
|
||||
"amount": "58.3807266225",
|
||||
"user": "0xcB7C51a63110F2669D33cadf593E838e7EdD8007",
|
||||
@ -63632,8 +63829,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "250",
|
||||
"withdrawn_tokens": "58.3807266225",
|
||||
"remaining_tokens": "191.6192733775"
|
||||
"withdrawn_tokens": "243.9662507625",
|
||||
"remaining_tokens": "6.0337492375"
|
||||
},
|
||||
{
|
||||
"address": "0x4d53f5e09adeAD6288477a37eede4522aacBBfdF",
|
||||
|
@ -38,7 +38,7 @@
|
||||
"tranche_end": "2022-11-26T13:48:10.000Z",
|
||||
"total_added": "100",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "49.106503678335867",
|
||||
"locked_amount": "48.558558472856417",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "100",
|
||||
@ -242,7 +242,7 @@
|
||||
"tranche_end": "2022-10-12T00:53:20.000Z",
|
||||
"total_added": "100",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "36.63031773211568",
|
||||
"locked_amount": "36.08237252663623",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "100",
|
||||
|
@ -3,6 +3,7 @@ import { DATA_SOURCES } from './config';
|
||||
import { Header } from './components/header';
|
||||
import { StatsManager } from '@vegaprotocol/network-stats';
|
||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
||||
import { EnvironmentProvider } from '@vegaprotocol/react-helpers';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const envName = DATA_SOURCES.envName;
|
||||
@ -14,19 +15,21 @@ function App() {
|
||||
const [theme, toggleTheme] = useThemeSwitcher();
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<div className="w-screen min-h-screen grid pb-24 bg-white text-black-95 dark:bg-black dark:text-white-80">
|
||||
<div className="layout-grid w-screen justify-self-center">
|
||||
<Header toggleTheme={toggleTheme} />
|
||||
<StatsManager
|
||||
envName={envName}
|
||||
statsEndpoint={statsEndpoint}
|
||||
nodesEndpoint={nodesEndpoint}
|
||||
className="max-w-3xl px-24"
|
||||
/>
|
||||
<EnvironmentProvider>
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<div className="w-screen min-h-screen grid pb-24 bg-white text-black-95 dark:bg-black dark:text-white-80">
|
||||
<div className="layout-grid w-screen justify-self-center">
|
||||
<Header toggleTheme={toggleTheme} />
|
||||
<StatsManager
|
||||
envName={envName}
|
||||
statsEndpoint={statsEndpoint}
|
||||
nodesEndpoint={nodesEndpoint}
|
||||
className="max-w-3xl px-24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ThemeContext.Provider>
|
||||
</ThemeContext.Provider>
|
||||
</EnvironmentProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
|
@ -20,37 +20,40 @@ import { Web3Provider } from '@vegaprotocol/web3';
|
||||
import { Connectors } from './lib/web3-connectors';
|
||||
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import { EnvironmentProvider } from '@vegaprotocol/react-helpers';
|
||||
|
||||
function App() {
|
||||
const sideBar = React.useMemo(() => [<EthWallet />, <VegaWallet />], []);
|
||||
return (
|
||||
<GraphQlProvider>
|
||||
<Router>
|
||||
<AppStateProvider>
|
||||
<Web3Provider connectors={Connectors}>
|
||||
<Web3Connector>
|
||||
<VegaWalletProvider>
|
||||
<ContractsProvider>
|
||||
<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">
|
||||
<AppBanner />
|
||||
<TemplateSidebar sidebar={sideBar}>
|
||||
<AppRouter />
|
||||
</TemplateSidebar>
|
||||
<AppFooter />
|
||||
</div>
|
||||
<VegaWalletDialogs />
|
||||
<TransactionModal />
|
||||
</>
|
||||
</BalanceManager>
|
||||
</AppLoader>
|
||||
</ContractsProvider>
|
||||
</VegaWalletProvider>
|
||||
</Web3Connector>
|
||||
</Web3Provider>
|
||||
</AppStateProvider>
|
||||
<EnvironmentProvider>
|
||||
<AppStateProvider>
|
||||
<Web3Provider connectors={Connectors}>
|
||||
<Web3Connector>
|
||||
<VegaWalletProvider>
|
||||
<ContractsProvider>
|
||||
<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">
|
||||
<AppBanner />
|
||||
<TemplateSidebar sidebar={sideBar}>
|
||||
<AppRouter />
|
||||
</TemplateSidebar>
|
||||
<AppFooter />
|
||||
</div>
|
||||
<VegaWalletDialogs />
|
||||
<TransactionModal />
|
||||
</>
|
||||
</BalanceManager>
|
||||
</AppLoader>
|
||||
</ContractsProvider>
|
||||
</VegaWalletProvider>
|
||||
</Web3Connector>
|
||||
</Web3Provider>
|
||||
</AppStateProvider>
|
||||
</EnvironmentProvider>
|
||||
</Router>
|
||||
</GraphQlProvider>
|
||||
);
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ADDRESSES } from '../../config';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { useAddAssetSupported } from '../../hooks/use-add-asset-to-wallet';
|
||||
import vegaVesting from '../../images/vega_vesting.png';
|
||||
import { AddTokenButtonLink } from '../add-token-button/add-token-button';
|
||||
import { Callout } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const AddLockedTokenAddress = () => {
|
||||
const { ADDRESSES } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const addSupported = useAddAssetSupported();
|
||||
return (
|
||||
|
@ -2,7 +2,7 @@ import * as Sentry from '@sentry/react';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React from 'react';
|
||||
|
||||
import { ADDRESSES } from '../../config';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
@ -17,6 +17,7 @@ interface BalanceManagerProps {
|
||||
}
|
||||
|
||||
export const BalanceManager = ({ children }: BalanceManagerProps) => {
|
||||
const { ADDRESSES } = useEnvironment();
|
||||
const contracts = useContracts();
|
||||
const { account } = useWeb3React();
|
||||
const { appDispatch } = useAppState();
|
||||
@ -55,7 +56,13 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
|
||||
};
|
||||
|
||||
updateBalances();
|
||||
}, [appDispatch, contracts?.token, contracts?.vesting, account]);
|
||||
}, [
|
||||
appDispatch,
|
||||
contracts?.token,
|
||||
contracts?.vesting,
|
||||
account,
|
||||
ADDRESSES.stakingBridge,
|
||||
]);
|
||||
|
||||
// This use effect hook is very expensive and is kept separate to prevent expensive reloading of data.
|
||||
React.useEffect(() => {
|
||||
|
@ -3,7 +3,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import type { TransactionState } from '../../hooks/transaction-reducer';
|
||||
import { TxState } from '../../hooks/transaction-reducer';
|
||||
import { truncateMiddle } from '../../lib/truncate-middle';
|
||||
import { Button, EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { Error, HandUp, Tick } from '../icons';
|
||||
import { Loader } from '../loader';
|
||||
import { StatefulButton } from '../stateful-button';
|
||||
@ -122,6 +123,7 @@ export const TransactionButtonFooter = ({
|
||||
txHash,
|
||||
message,
|
||||
}: TransactionButtonFooterProps) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (message) {
|
||||
@ -142,7 +144,12 @@ export const TransactionButtonFooter = ({
|
||||
<div className="transaction-button__footer">
|
||||
<p className="flex justify-between items-start m-0 text-ui">
|
||||
<span>{t('transaction')}</span>
|
||||
<EtherscanLink text={truncateMiddle(txHash)} tx={txHash} />
|
||||
<Link
|
||||
href={`${ETHERSCAN_URL}/tx/${txHash}`}
|
||||
title={t('View on Etherscan')}
|
||||
>
|
||||
{truncateMiddle(txHash)}
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Callout, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
export const TransactionComplete = ({
|
||||
@ -14,6 +15,7 @@ export const TransactionComplete = ({
|
||||
footer?: ReactElement | string;
|
||||
body?: ReactElement | string;
|
||||
}) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Callout
|
||||
@ -23,7 +25,12 @@ export const TransactionComplete = ({
|
||||
>
|
||||
{body && <p data-testid="transaction-complete-body">{body}</p>}
|
||||
<p>
|
||||
<EtherscanLink tx={hash} />
|
||||
<Link
|
||||
title={t('View transaction on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${hash}`}
|
||||
>
|
||||
{hash}
|
||||
</Link>
|
||||
</p>
|
||||
{footer && <p data-testid="transaction-complete-footer">{footer}</p>}
|
||||
</Callout>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Button, Callout, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { Error } from '../icons';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export interface TransactionErrorProps {
|
||||
error: Error | null;
|
||||
@ -15,6 +15,7 @@ export const TransactionError = ({
|
||||
hash,
|
||||
onActionClick,
|
||||
}: TransactionErrorProps) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@ -22,7 +23,12 @@ export const TransactionError = ({
|
||||
<p>{error ? error.message : t('Something went wrong')}</p>
|
||||
{hash ? (
|
||||
<p>
|
||||
<EtherscanLink tx={hash} />
|
||||
<Link
|
||||
title={t('View transaction on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${hash}`}
|
||||
>
|
||||
{hash}
|
||||
</Link>
|
||||
</p>
|
||||
) : null}
|
||||
<Button onClick={() => onActionClick()}>{t('Try again')}</Button>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Callout } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const TransactionPending = ({
|
||||
hash,
|
||||
@ -18,6 +19,7 @@ export const TransactionPending = ({
|
||||
footer?: React.ReactElement | string;
|
||||
body?: React.ReactElement | string;
|
||||
}) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const remainingConfirmations = React.useMemo(() => {
|
||||
if (requiredConfirmations) {
|
||||
@ -38,7 +40,12 @@ export const TransactionPending = ({
|
||||
<Callout iconName="refresh" title={title}>
|
||||
{body && <p data-testid="transaction-pending-body">{body}</p>}
|
||||
<p>
|
||||
<EtherscanLink tx={hash} />
|
||||
<Link
|
||||
title={t('View transaction on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${hash}`}
|
||||
>
|
||||
{hash}
|
||||
</Link>
|
||||
</p>
|
||||
{footer && <p data-testid="transaction-pending-footer">{footer}</p>}
|
||||
</Callout>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { TxData } from '@vegaprotocol/smart-contracts';
|
||||
import { Dialog, EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { Dialog, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -28,6 +29,7 @@ const TransactionModalStatus = ({
|
||||
}) => <span className="flex gap-4 items-center">{children}</span>;
|
||||
|
||||
export const TransactionModal = () => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const { transactions } = useContracts();
|
||||
const { appState, appDispatch } = useAppState();
|
||||
@ -76,16 +78,20 @@ export const TransactionModal = () => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{transactions.map((t) => {
|
||||
{transactions.map((transaction) => {
|
||||
return (
|
||||
<tr key={t.tx.hash}>
|
||||
<tr key={transaction.tx.hash}>
|
||||
<TransactionModalTd>
|
||||
<EtherscanLink
|
||||
tx={t.tx.hash}
|
||||
text={truncateMiddle(t.tx.hash)}
|
||||
/>
|
||||
<Link
|
||||
title={t('View transaction on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${transaction.tx.hash}`}
|
||||
>
|
||||
{truncateMiddle(transaction.tx.hash)}
|
||||
</Link>
|
||||
</TransactionModalTd>
|
||||
<TransactionModalTd>
|
||||
{renderStatus(transaction)}
|
||||
</TransactionModalTd>
|
||||
<TransactionModalTd>{renderStatus(t)}</TransactionModalTd>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
@ -6,7 +6,6 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { AccountType } from '../../__generated__/globalTypes';
|
||||
import { ADDRESSES } from '../../config';
|
||||
import noIcon from '../../images/token-no-icon.png';
|
||||
import vegaBlack from '../../images/vega_black.png';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
@ -18,6 +17,7 @@ import type {
|
||||
DelegationsVariables,
|
||||
} from './__generated__/Delegations';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const DELEGATIONS_QUERY = gql`
|
||||
query Delegations($partyId: ID!) {
|
||||
@ -60,6 +60,7 @@ const DELEGATIONS_QUERY = gql`
|
||||
`;
|
||||
|
||||
export const usePollForDelegations = () => {
|
||||
const { ADDRESSES } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const { keypair } = useVegaWallet();
|
||||
const client = useApolloClient();
|
||||
@ -227,7 +228,7 @@ export const usePollForDelegations = () => {
|
||||
clearInterval(interval);
|
||||
mounted = false;
|
||||
};
|
||||
}, [client, keypair?.pub, t]);
|
||||
}, [client, keypair?.pub, t, ADDRESSES.vegaTokenAddress]);
|
||||
|
||||
return { delegations, currentStakeAvailable, delegatedNodes, accounts };
|
||||
};
|
||||
|
@ -1,30 +0,0 @@
|
||||
import type { EthereumChainId } from '@vegaprotocol/smart-contracts';
|
||||
import {
|
||||
EnvironmentConfig,
|
||||
EthereumChainIds,
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
|
||||
import type { Networks } from './vega';
|
||||
|
||||
type VegaContracts = typeof EnvironmentConfig[Networks];
|
||||
|
||||
const appChainId = Number(process.env['NX_ETHEREUM_CHAIN_ID'] || 3);
|
||||
|
||||
export const APP_ENV = process.env['NX_VEGA_ENV'] as Networks;
|
||||
|
||||
const Addresses: Record<number, VegaContracts> = {
|
||||
1: EnvironmentConfig.MAINNET,
|
||||
3: EnvironmentConfig[APP_ENV],
|
||||
};
|
||||
|
||||
export type { EthereumChainId };
|
||||
export { EthereumChainIds };
|
||||
|
||||
/** Contract addresses for the different contracts in the VEGA ecosystem */
|
||||
export const ADDRESSES = Addresses[appChainId];
|
||||
|
||||
/**
|
||||
* The Chain ID the environment is configured for.
|
||||
* Normally this is 0x3 (Ropsten) for dev and 0x1 (Mainnet) for prod
|
||||
*/
|
||||
export const APP_CHAIN_ID = appChainId;
|
@ -1,5 +1,4 @@
|
||||
export * from './flags';
|
||||
export * from './ethereum';
|
||||
export * from './links';
|
||||
export * from './network-params';
|
||||
export * from './vega';
|
||||
|
@ -1,11 +1,4 @@
|
||||
export enum Networks {
|
||||
CUSTOM = 'CUSTOM',
|
||||
TESTNET = 'TESTNET',
|
||||
STAGNET = 'STAGNET',
|
||||
STAGNET2 = 'STAGNET2',
|
||||
DEVNET = 'DEVNET',
|
||||
MAINNET = 'MAINNET',
|
||||
}
|
||||
import { Networks } from '@vegaprotocol/smart-contracts';
|
||||
|
||||
interface VegaNode {
|
||||
url: string;
|
||||
|
@ -10,9 +10,9 @@ import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import React from 'react';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import { SplashLoader } from '../../components/splash-loader';
|
||||
import { ADDRESSES, APP_ENV } from '../../config';
|
||||
import type { ContractsContextShape } from './contracts-context';
|
||||
import { ContractsContext } from './contracts-context';
|
||||
import { defaultProvider } from '../../lib/web3-connectors';
|
||||
@ -21,6 +21,7 @@ import { defaultProvider } from '../../lib/web3-connectors';
|
||||
* Provides Vega Ethereum contract instances to its children.
|
||||
*/
|
||||
export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
const { ADDRESSES, VEGA_ENV } = useEnvironment();
|
||||
const { provider: activeProvider, account } = useWeb3React();
|
||||
const [txs, setTxs] = React.useState<TxData[]>([]);
|
||||
const [contracts, setContracts] = React.useState<Pick<
|
||||
@ -53,20 +54,20 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
signer
|
||||
),
|
||||
// @ts-ignore Cant accept JsonRpcProvider provider
|
||||
staking: new VegaStaking(APP_ENV, provider, signer),
|
||||
staking: new VegaStaking(VEGA_ENV, provider, signer),
|
||||
// @ts-ignore Cant accept JsonRpcProvider provider
|
||||
vesting: new VegaVesting(APP_ENV, provider, signer),
|
||||
vesting: new VegaVesting(VEGA_ENV, provider, signer),
|
||||
// @ts-ignore Cant accept JsonRpcProvider provider
|
||||
claim: new VegaClaim(APP_ENV, provider, signer),
|
||||
claim: new VegaClaim(VEGA_ENV, provider, signer),
|
||||
erc20Bridge: new VegaErc20Bridge(
|
||||
APP_ENV,
|
||||
VEGA_ENV,
|
||||
// @ts-ignore Cant accept JsonRpcProvider provider
|
||||
provider,
|
||||
signer
|
||||
),
|
||||
});
|
||||
}
|
||||
}, [activeProvider, account]);
|
||||
}, [activeProvider, account, ADDRESSES.vegaTokenAddress, VEGA_ENV]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!contracts) return;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { MetaMask } from '@web3-react/metamask';
|
||||
import React from 'react';
|
||||
|
||||
import { APP_ENV, Networks } from '../config';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { Networks } from '@vegaprotocol/smart-contracts';
|
||||
|
||||
export const useAddAssetSupported = () => {
|
||||
const { connector } = useWeb3React();
|
||||
@ -19,6 +19,7 @@ export const useAddAssetToWallet = (
|
||||
decimals: number,
|
||||
image: string
|
||||
) => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const { provider } = useWeb3React();
|
||||
const addSupported = useAddAssetSupported();
|
||||
const add = React.useCallback(async () => {
|
||||
@ -35,10 +36,10 @@ export const useAddAssetToWallet = (
|
||||
address,
|
||||
symbol: `${symbol}${
|
||||
// Add the environment if not mainnet
|
||||
APP_ENV === Networks.MAINNET
|
||||
VEGA_ENV === Networks.MAINNET
|
||||
? ''
|
||||
: // Remove NET as VEGA(TESTNET) is too long
|
||||
` ${APP_ENV.replace('NET', '')}`
|
||||
` ${VEGA_ENV.replace('NET', '')}`
|
||||
}`,
|
||||
decimals,
|
||||
image,
|
||||
@ -48,7 +49,7 @@ export const useAddAssetToWallet = (
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
}, [address, decimals, image, provider, symbol]);
|
||||
}, [address, decimals, image, provider, symbol, VEGA_ENV]);
|
||||
|
||||
return React.useMemo(() => {
|
||||
return {
|
||||
|
@ -2,7 +2,7 @@ import * as Sentry from '@sentry/react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import React from 'react';
|
||||
|
||||
import { ADDRESSES } from '../config';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
@ -10,6 +10,7 @@ import {
|
||||
import { useContracts } from '../contexts/contracts/contracts-context';
|
||||
|
||||
export const useRefreshBalances = (address: string) => {
|
||||
const { ADDRESSES } = useEnvironment();
|
||||
const { appDispatch } = useAppState();
|
||||
const { keypair } = useVegaWallet();
|
||||
const { token, staking, vesting } = useContracts();
|
||||
@ -44,5 +45,13 @@ export const useRefreshBalances = (address: string) => {
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
}, [address, appDispatch, keypair?.pub, staking, token, vesting]);
|
||||
}, [
|
||||
address,
|
||||
appDispatch,
|
||||
keypair?.pub,
|
||||
staking,
|
||||
token,
|
||||
vesting,
|
||||
ADDRESSES.stakingBridge,
|
||||
]);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type { Networks, Tranche } from '@vegaprotocol/smart-contracts';
|
||||
import React, { useEffect } from 'react';
|
||||
import { APP_ENV } from '../config';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import { BigNumber } from '../lib/bignumber';
|
||||
|
||||
@ -15,8 +15,9 @@ const TRANCHES_URLS: { [N in Networks]: string } = {
|
||||
};
|
||||
|
||||
export function useTranches() {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const [tranches, setTranches] = React.useState<Tranche[] | null>(null);
|
||||
const url = React.useMemo(() => TRANCHES_URLS[APP_ENV], []);
|
||||
const url = React.useMemo(() => TRANCHES_URLS[VEGA_ENV], [VEGA_ENV]);
|
||||
const {
|
||||
state: { data, loading, error },
|
||||
} = useFetch<Tranche[] | null>(url);
|
||||
|
@ -1,11 +1,7 @@
|
||||
import {
|
||||
Callout,
|
||||
Intent,
|
||||
EtherscanLink,
|
||||
Button,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Callout, Intent, Link, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link as RouteLink } from 'react-router-dom';
|
||||
|
||||
import type { BigNumber } from '../../lib/bignumber';
|
||||
import { formatNumber } from '../../lib/format-number';
|
||||
@ -22,6 +18,7 @@ export const Complete = ({
|
||||
commitTxHash: string | null;
|
||||
claimTxHash: string | null;
|
||||
}) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@ -38,18 +35,28 @@ export const Complete = ({
|
||||
{commitTxHash && (
|
||||
<p style={{ margin: 0 }}>
|
||||
{t('Link transaction')}:{' '}
|
||||
<EtherscanLink tx={commitTxHash} text={commitTxHash} />
|
||||
<Link
|
||||
title={t('View transaction on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${commitTxHash}`}
|
||||
>
|
||||
{commitTxHash}
|
||||
</Link>
|
||||
</p>
|
||||
)}
|
||||
{claimTxHash && (
|
||||
<p>
|
||||
{t('Claim transaction')}:{' '}
|
||||
<EtherscanLink tx={claimTxHash} text={claimTxHash} />
|
||||
<Link
|
||||
title={t('View transaction on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${claimTxHash}`}
|
||||
>
|
||||
{claimTxHash}
|
||||
</Link>
|
||||
</p>
|
||||
)}
|
||||
<Link to={Routes.VESTING}>
|
||||
<RouteLink to={Routes.VESTING}>
|
||||
<Button className="fill">{t('Check your vesting VEGA tokens')}</Button>
|
||||
</Link>
|
||||
</RouteLink>
|
||||
</Callout>
|
||||
);
|
||||
};
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { Heading } from '../../components/heading';
|
||||
import { ADDRESSES } from '../../config';
|
||||
|
||||
const Contracts = () => {
|
||||
const { ADDRESSES, ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<section>
|
||||
<Heading title={'Contracts'} />
|
||||
@ -10,7 +13,12 @@ const Contracts = () => {
|
||||
{Object.entries(ADDRESSES).map(([key, value]) => (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div>{key}:</div>
|
||||
<EtherscanLink address={value as string} text={value as string} />
|
||||
<Link
|
||||
title={t('View address on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/address/${value}`}
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
|
@ -1,13 +1,8 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
Callout,
|
||||
EtherscanLink,
|
||||
Intent,
|
||||
Splash,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Callout, Link, Intent, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
|
||||
import { ADDRESSES } from '../../../config';
|
||||
import { useTranches } from '../../../hooks/use-tranches';
|
||||
import type { BigNumber } from '../../../lib/bignumber';
|
||||
import { formatNumber } from '../../../lib/format-number';
|
||||
@ -21,6 +16,7 @@ export const TokenDetails = ({
|
||||
totalSupply: BigNumber;
|
||||
totalStaked: BigNumber;
|
||||
}) => {
|
||||
const { ADDRESSES, ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { tranches, loading, error } = useTranches();
|
||||
@ -45,21 +41,25 @@ export const TokenDetails = ({
|
||||
<KeyValueTable className={'token-details'}>
|
||||
<KeyValueTableRow>
|
||||
{t('Token address').toUpperCase()}
|
||||
<EtherscanLink
|
||||
<Link
|
||||
data-testid="token-address"
|
||||
address={ADDRESSES.vegaTokenAddress}
|
||||
text={ADDRESSES.vegaTokenAddress}
|
||||
title={t('View address on Etherscan')}
|
||||
className="font-mono"
|
||||
/>
|
||||
href={`${ETHERSCAN_URL}/address/${ADDRESSES.vegaTokenAddress}`}
|
||||
>
|
||||
{ADDRESSES.vegaTokenAddress}
|
||||
</Link>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('Vesting contract'.toUpperCase())}
|
||||
<EtherscanLink
|
||||
<Link
|
||||
data-testid="token-contract"
|
||||
address={ADDRESSES.vestingAddress}
|
||||
text={ADDRESSES.vestingAddress}
|
||||
title={t('View address on Etherscan')}
|
||||
className="font-mono"
|
||||
/>
|
||||
href={`${ETHERSCAN_URL}/address/${ADDRESSES.vestingAddress}`}
|
||||
>
|
||||
{ADDRESSES.vestingAddress}
|
||||
</Link>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('Total supply').toUpperCase()}
|
||||
|
@ -3,7 +3,7 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Link, useParams, useOutletContext } from 'react-router-dom';
|
||||
|
||||
import { TransactionCallout } from '../../../components/transaction-callout';
|
||||
import { ADDRESSES } from '../../../config';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
import { useContracts } from '../../../contexts/contracts/contracts-context';
|
||||
import {
|
||||
@ -25,6 +25,7 @@ export const RedeemFromTranche = () => {
|
||||
address: string;
|
||||
}>();
|
||||
const { vesting } = useContracts();
|
||||
const { ADDRESSES } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
appState: { lien, totalVestedBalance, trancheBalances, totalLockedBalance },
|
||||
|
@ -1,12 +1,8 @@
|
||||
import {
|
||||
Button,
|
||||
Callout,
|
||||
EtherscanLink,
|
||||
Intent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Button, Callout, Link, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link as RouteLink } from 'react-router-dom';
|
||||
|
||||
import { TransactionCallout } from '../../../components/transaction-callout';
|
||||
import type {
|
||||
@ -35,6 +31,7 @@ export const AssociateTransaction = ({
|
||||
requiredConfirmations: number;
|
||||
linking: PartyStakeLinkings_party_stake_linkings | null;
|
||||
}) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const remainingConfirmations = React.useMemo(() => {
|
||||
@ -71,7 +68,12 @@ export const AssociateTransaction = ({
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
<EtherscanLink tx={state.txData.hash || ''} />
|
||||
<Link
|
||||
title={t('View transaction on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${state.txData.hash}`}
|
||||
>
|
||||
{state.txData.hash}
|
||||
</Link>
|
||||
</p>
|
||||
<p data-testid="transaction-pending-footer">
|
||||
{t('pendingAssociationText', {
|
||||
@ -90,11 +92,11 @@ export const AssociateTransaction = ({
|
||||
{ vegaKey }
|
||||
)}
|
||||
completeFooter={
|
||||
<Link to={Routes.STAKING}>
|
||||
<RouteLink to={Routes.STAKING}>
|
||||
<Button className="fill">
|
||||
{t('Nominate Stake to Validator Node')}
|
||||
</Button>
|
||||
</Link>
|
||||
</RouteLink>
|
||||
}
|
||||
pendingHeading={t('Associating Tokens')}
|
||||
pendingBody={t(
|
||||
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { TokenInput } from '../../../components/token-input';
|
||||
import { ADDRESSES } from '../../../config';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
@ -13,6 +12,7 @@ import { useTransaction } from '../../../hooks/use-transaction';
|
||||
import { BigNumber } from '../../../lib/bignumber';
|
||||
import { AssociateInfo } from './associate-info';
|
||||
import type { VegaKeyExtended } from '@vegaprotocol/wallet';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const WalletAssociate = ({
|
||||
perform,
|
||||
@ -27,6 +27,7 @@ export const WalletAssociate = ({
|
||||
vegaKey: VegaKeyExtended;
|
||||
address: string;
|
||||
}) => {
|
||||
const { ADDRESSES } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
appDispatch,
|
||||
@ -56,7 +57,13 @@ export const WalletAssociate = ({
|
||||
}
|
||||
};
|
||||
run();
|
||||
}, [address, appDispatch, approveState.txState, token]);
|
||||
}, [
|
||||
address,
|
||||
appDispatch,
|
||||
approveState.txState,
|
||||
token,
|
||||
ADDRESSES.stakingBridge,
|
||||
]);
|
||||
|
||||
let pageContent = null;
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Button, Callout, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link as RouteLink } from 'react-router-dom';
|
||||
|
||||
import { BulletHeader } from '../../components/bullet-header';
|
||||
import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { Links } from '../../config';
|
||||
import {
|
||||
AppStateActionType,
|
||||
@ -29,14 +30,9 @@ export const Staking = ({ data }: { data?: StakingQueryResult }) => {
|
||||
<p className="mb-12">{t('stakingDescription3')}</p>
|
||||
<p className="mb-12">{t('stakingDescription4')}</p>
|
||||
<p className="mb-12">
|
||||
<a
|
||||
className="underline"
|
||||
href={Links.STAKING_GUIDE}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Link href={Links.STAKING_GUIDE} target="_blank">
|
||||
{t('readMoreStaking')}
|
||||
</a>
|
||||
</Link>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@ -65,6 +61,7 @@ export const Staking = ({ data }: { data?: StakingQueryResult }) => {
|
||||
};
|
||||
|
||||
export const StakingStepConnectWallets = () => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const { account } = useWeb3React();
|
||||
const { keypair } = useVegaWallet();
|
||||
@ -75,7 +72,12 @@ export const StakingStepConnectWallets = () => {
|
||||
<Callout intent={Intent.Success} iconName="tick" title={'Connected'}>
|
||||
<p>
|
||||
{t('Connected Ethereum address')}
|
||||
<EtherscanLink address={account} text={account} />
|
||||
<Link
|
||||
title={t('View address on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${account}`}
|
||||
>
|
||||
{account}
|
||||
</Link>
|
||||
</p>
|
||||
<p>
|
||||
{t('stakingVegaWalletConnected', {
|
||||
@ -94,7 +96,7 @@ export const StakingStepConnectWallets = () => {
|
||||
components={{
|
||||
vegaWalletLink: (
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
<a href={Links.WALLET_GUIDE} target="_blank" rel="noreferrer" />
|
||||
<Link href={Links.WALLET_GUIDE} target="_blank" />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
@ -171,17 +173,17 @@ export const StakingStepAssociate = ({
|
||||
title={t('stakingHasAssociated', { tokens: formatNumber(associated) })}
|
||||
>
|
||||
<p>
|
||||
<Link to="/staking/associate">
|
||||
<RouteLink to="/staking/associate">
|
||||
<Button data-testid="associate-more-tokens-btn">
|
||||
{t('stakingAssociateMoreButton')}
|
||||
</Button>
|
||||
</Link>
|
||||
</RouteLink>
|
||||
</p>
|
||||
<Link to="/staking/disassociate">
|
||||
<RouteLink to="/staking/disassociate">
|
||||
<Button data-testid="disassociate-tokens-btn">
|
||||
{t('stakingDisassociateButton')}
|
||||
</Button>
|
||||
</Link>
|
||||
</RouteLink>
|
||||
</Callout>
|
||||
);
|
||||
}
|
||||
@ -189,11 +191,11 @@ export const StakingStepAssociate = ({
|
||||
return (
|
||||
<>
|
||||
<p>{t('stakingStep2Text')}</p>
|
||||
<Link to="/staking/associate">
|
||||
<RouteLink to="/staking/associate">
|
||||
<Button data-testid="associate-tokens-btn">
|
||||
{t('associateButton')}
|
||||
</Button>
|
||||
</Link>
|
||||
</RouteLink>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import { formatNumber } from '../../lib/format-number';
|
||||
@ -22,6 +23,7 @@ export const ValidatorTable = ({
|
||||
stakedTotal,
|
||||
stakeThisEpoch,
|
||||
}: ValidatorTableProps) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const stakePercentage = React.useMemo(() => {
|
||||
const total = new BigNumber(stakedTotal);
|
||||
@ -56,10 +58,12 @@ export const ValidatorTable = ({
|
||||
<KeyValueTableRow>
|
||||
<span>{t('ETHEREUM ADDRESS')}</span>
|
||||
<span>
|
||||
<EtherscanLink
|
||||
text={node.ethereumAdddress}
|
||||
address={node.ethereumAdddress}
|
||||
/>
|
||||
<Link
|
||||
title={t('View address on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/address/${node.ethereumAdddress}`}
|
||||
>
|
||||
{node.ethereumAdddress}
|
||||
</Link>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { ADDRESSES, EthereumChainIds } from '../../config';
|
||||
import {
|
||||
EthereumChainIds,
|
||||
EnvironmentConfig,
|
||||
Networks,
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import type { TrancheLabelProps } from './tranche-label';
|
||||
import { TrancheLabel } from './tranche-label';
|
||||
|
||||
@ -9,7 +13,7 @@ let props: TrancheLabelProps;
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
chainId: EthereumChainIds.Mainnet,
|
||||
contract: ADDRESSES.vestingAddress,
|
||||
contract: EnvironmentConfig[Networks.MAINNET].vestingAddress,
|
||||
id: 5,
|
||||
};
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ADDRESSES, EthereumChainIds } from '../../config';
|
||||
import type { EthereumChainId } from '../../config';
|
||||
import type { EthereumChainId } from '@vegaprotocol/smart-contracts';
|
||||
import { EthereumChainIds } from '@vegaprotocol/smart-contracts';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const TRANCHE_LABELS: Record<number, string[]> = {
|
||||
'5': ['Coinlist Option 1', 'Community Whitelist'],
|
||||
@ -26,6 +27,7 @@ export interface TrancheLabelProps {
|
||||
* @param id The tranche ID on this contract
|
||||
*/
|
||||
export const TrancheLabel = ({ contract, chainId, id }: TrancheLabelProps) => {
|
||||
const { ADDRESSES } = useEnvironment();
|
||||
// Only mainnet tranches on the known vesting contract have useful name
|
||||
if (
|
||||
chainId &&
|
||||
|
@ -1,4 +1,7 @@
|
||||
import type { Tranche as ITranche } from '@vegaprotocol/smart-contracts';
|
||||
import type {
|
||||
Tranche as ITranche,
|
||||
EthereumChainId,
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -6,9 +9,8 @@ import { useParams } from 'react-router';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { EthereumChainId } from '../../config';
|
||||
import { ADDRESSES } from '../../config';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import { formatNumber } from '../../lib/format-number';
|
||||
import { TrancheItem } from '../redemption/tranche-item';
|
||||
@ -27,6 +29,7 @@ const TrancheProgressContents = ({
|
||||
|
||||
export const Tranche = () => {
|
||||
const tranches = useOutletContext<ITranche[]>();
|
||||
const { ADDRESSES, ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const { trancheId } = useParams<{ trancheId: string }>();
|
||||
const { chainId } = useWeb3React();
|
||||
@ -81,7 +84,12 @@ export const Tranche = () => {
|
||||
const locked = user.remaining_tokens.times(lockedData?.locked || 0);
|
||||
return (
|
||||
<li className="pb-4" key={i}>
|
||||
<EtherscanLink address={user.address} text={user.address} />
|
||||
<Link
|
||||
title={t('View address on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${user.address}`}
|
||||
>
|
||||
{user.address}
|
||||
</Link>
|
||||
<TrancheProgressContents>
|
||||
<span>{t('Locked')}</span>
|
||||
<span>{t('Unlocked')}</span>
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
import type { Tranche } from '@vegaprotocol/smart-contracts';
|
||||
import type { Tranche, EthereumChainId } from '@vegaprotocol/smart-contracts';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { EthereumChainId } from '../../config';
|
||||
import { ADDRESSES } from '../../config';
|
||||
import { TrancheItem } from '../redemption/tranche-item';
|
||||
import { TrancheLabel } from './tranche-label';
|
||||
import { VestingChart } from './vesting-chart';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const trancheMinimum = 10;
|
||||
|
||||
@ -17,6 +16,7 @@ const shouldShowTranche = (t: Tranche) =>
|
||||
!t.total_added.isLessThanOrEqualTo(trancheMinimum);
|
||||
|
||||
export const Tranches = () => {
|
||||
const { ADDRESSES } = useEnvironment();
|
||||
const tranches = useOutletContext<Tranche[]>();
|
||||
const [showAll, setShowAll] = React.useState<boolean>(false);
|
||||
const { t } = useTranslation();
|
||||
|
@ -4,7 +4,8 @@ import orderBy from 'lodash/orderBy';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { Heading } from '../../components/heading';
|
||||
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
|
||||
import { SplashLoader } from '../../components/splash-loader';
|
||||
@ -99,6 +100,7 @@ interface WithdrawalProps {
|
||||
}
|
||||
|
||||
export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const renderStatus = ({
|
||||
@ -148,12 +150,12 @@ export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
|
||||
<KeyValueTableRow>
|
||||
{t('toEthereum')}
|
||||
<span>
|
||||
<EtherscanLink
|
||||
address={withdrawal.details?.receiverAddress as string}
|
||||
text={truncateMiddle(
|
||||
withdrawal.details?.receiverAddress as string
|
||||
)}
|
||||
/>
|
||||
<Link
|
||||
title={t('View address on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${withdrawal.details?.receiverAddress}`}
|
||||
>
|
||||
{truncateMiddle(withdrawal.details?.receiverAddress ?? '')}
|
||||
</Link>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
@ -169,10 +171,12 @@ export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
|
||||
{t('withdrawalTransaction', { foreignChain: 'Ethereum' })}
|
||||
<span>
|
||||
{withdrawal.txHash ? (
|
||||
<EtherscanLink
|
||||
tx={withdrawal.txHash}
|
||||
text={truncateMiddle(withdrawal.txHash)}
|
||||
/>
|
||||
<Link
|
||||
title={t('View transaction on Etherscan')}
|
||||
href={`${ETHERSCAN_URL}/tx/${withdrawal.txHash}`}
|
||||
>
|
||||
{truncateMiddle(withdrawal.txHash)}
|
||||
</Link>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
|
@ -10,8 +10,7 @@
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"resolveJsonModule": true
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
|
@ -10,6 +10,7 @@ export const generateDealTicketQuery = (
|
||||
market: {
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 1,
|
||||
state: MarketState.Active,
|
||||
tradingMode: MarketTradingMode.Continuous,
|
||||
tradableInstrument: {
|
||||
|
77
apps/trading-e2e/src/support/mocks/generate-market-list.ts
Normal file
77
apps/trading-e2e/src/support/mocks/generate-market-list.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import merge from 'lodash/merge';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { MarketList, MarketList_markets } from '@vegaprotocol/market-list';
|
||||
|
||||
export const generateMarketList = (
|
||||
override?: PartialDeep<MarketList>
|
||||
): MarketList => {
|
||||
const markets: MarketList_markets[] = [
|
||||
{
|
||||
id: 'market-id',
|
||||
decimalPlaces: 5,
|
||||
data: {
|
||||
market: {
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
__typename: 'Market',
|
||||
},
|
||||
markPrice: '4612690058',
|
||||
__typename: 'MarketData',
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'BTC/USD Monthly',
|
||||
code: 'BTCUSD.MF21',
|
||||
metadata: {
|
||||
__typename: 'InstrumentMetadata',
|
||||
tags: ['tag1'],
|
||||
},
|
||||
__typename: 'Instrument',
|
||||
},
|
||||
__typename: 'TradableInstrument',
|
||||
},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '',
|
||||
close: '',
|
||||
},
|
||||
candles: [{ __typename: 'Candle', open: '100', close: '100' }],
|
||||
__typename: 'Market',
|
||||
},
|
||||
{
|
||||
id: 'test-market-suspended',
|
||||
decimalPlaces: 2,
|
||||
data: {
|
||||
market: {
|
||||
id: '34d95e10faa00c21d19d382d6d7e6fc9722a96985369f0caec041b0f44b775ed',
|
||||
__typename: 'Market',
|
||||
},
|
||||
markPrice: '8441',
|
||||
__typename: 'MarketData',
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'SOL/USD',
|
||||
code: 'SOLUSD',
|
||||
metadata: {
|
||||
__typename: 'InstrumentMetadata',
|
||||
tags: ['tag1'],
|
||||
},
|
||||
__typename: 'Instrument',
|
||||
},
|
||||
__typename: 'TradableInstrument',
|
||||
},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '',
|
||||
close: '',
|
||||
},
|
||||
candles: [{ __typename: 'Candle', open: '100', close: '100' }],
|
||||
__typename: 'Market',
|
||||
},
|
||||
];
|
||||
const defaultResult = {
|
||||
markets,
|
||||
};
|
||||
|
||||
return merge(defaultResult, override);
|
||||
};
|
@ -31,7 +31,7 @@ export default class WithdrawalsPage extends BasePage {
|
||||
validateConnectWalletText() {
|
||||
cy.getByTestId(this.connectVegaWalletText).should(
|
||||
'have.text',
|
||||
'Please connect your Vega wallet'
|
||||
'Connect your Vega wallet'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,18 @@
|
||||
import { Given } from 'cypress-cucumber-preprocessor/steps';
|
||||
import { hasOperationName } from '..';
|
||||
import { generateMarketList } from '../mocks/generate-market-list';
|
||||
import BasePage from '../pages/base-page';
|
||||
|
||||
const basePage = new BasePage();
|
||||
|
||||
Given('I am on the homepage', () => {
|
||||
cy.mockGQL('MarketsList', (req) => {
|
||||
if (hasOperationName(req, 'MarketsList')) {
|
||||
req.reply({
|
||||
body: { data: generateMarketList() },
|
||||
});
|
||||
}
|
||||
});
|
||||
cy.visit('/');
|
||||
basePage.closeDialog();
|
||||
});
|
||||
|
@ -11,7 +11,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
|
1
apps/trading/components/vega-wallet-container/index.ts
Normal file
1
apps/trading/components/vega-wallet-container/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './vega-wallet-container';
|
@ -0,0 +1,27 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { VegaWalletContainer } from './vega-wallet-container';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
const generateJsx = (context: PartialDeep<VegaWalletContextShape>) => {
|
||||
return (
|
||||
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
|
||||
<VegaWalletContainer>
|
||||
<div data-testid="child" />
|
||||
</VegaWalletContainer>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('VegaWalletContainer', () => {
|
||||
it('doesnt render children if not connected', () => {
|
||||
render(generateJsx({ keypair: null }));
|
||||
expect(screen.queryByTestId('child')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders children if connected', () => {
|
||||
render(generateJsx({ keypair: { pub: '0x123' } }));
|
||||
expect(screen.getByTestId('child')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1,34 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
interface VegaWalletContainerProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
|
||||
const store = useGlobalStore();
|
||||
const { keypair } = useVegaWallet();
|
||||
|
||||
if (!keypair) {
|
||||
return (
|
||||
<Splash>
|
||||
<div className="text-center">
|
||||
<p className="mb-12" data-testid="connect-vega-wallet-text">
|
||||
{t('Connect your Vega wallet')}
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => store.setVegaWalletConnectDialog(true)}
|
||||
data-testid="vega-wallet-connect"
|
||||
>
|
||||
{t('Connect')}
|
||||
</Button>
|
||||
</div>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
@ -108,11 +108,7 @@ export const Web3Content = ({
|
||||
const { isActive, error, connector, chainId } = useWeb3React();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
connector?.connectEagerly &&
|
||||
// Dont eager connect if this is a cypress test run
|
||||
'Cypress' in window
|
||||
) {
|
||||
if (connector?.connectEagerly) {
|
||||
connector.connectEagerly();
|
||||
}
|
||||
}, [connector]);
|
||||
|
@ -7,81 +7,85 @@ import {
|
||||
VegaManageDialog,
|
||||
VegaWalletProvider,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { EnvironmentProvider } from '@vegaprotocol/react-helpers';
|
||||
import { Connectors } from '../lib/vega-connectors';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { createClient } from '../lib/apollo-client';
|
||||
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
|
||||
import { ApolloProvider } from '@apollo/client';
|
||||
import { AppLoader } from '../components/app-loader';
|
||||
import { VegaWalletConnectButton } from '../components/vega-wallet-connect-button';
|
||||
import './styles.css';
|
||||
import { useGlobalStore } from '../stores';
|
||||
|
||||
function VegaTradingApp({ Component, pageProps }: AppProps) {
|
||||
const client = useMemo(() => createClient(process.env['NX_VEGA_URL']), []);
|
||||
const [vegaWallet, setVegaWallet] = useState({
|
||||
connect: false,
|
||||
manage: false,
|
||||
});
|
||||
const store = useGlobalStore();
|
||||
const [theme, toggleTheme] = useThemeSwitcher();
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<ApolloProvider client={client}>
|
||||
<VegaWalletProvider>
|
||||
<AppLoader>
|
||||
<Head>
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
<title>{t('Welcome to Vega trading!')}</title>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||
</Head>
|
||||
<div className="h-full dark:bg-black dark:text-white-60 bg-white relative z-0 text-black-60 grid grid-rows-[min-content,1fr]">
|
||||
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
|
||||
<Navbar />
|
||||
<div className="flex items-center gap-4 ml-auto mr-8">
|
||||
<VegaWalletConnectButton
|
||||
setConnectDialog={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, connect: open }))
|
||||
}
|
||||
setManageDialog={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, manage: open }))
|
||||
}
|
||||
/>
|
||||
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
|
||||
<EnvironmentProvider>
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<ApolloProvider client={client}>
|
||||
<VegaWalletProvider>
|
||||
<AppLoader>
|
||||
<Head>
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
<title>{t('Welcome to Vega trading!')}</title>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://static.vega.xyz/fonts.css"
|
||||
/>
|
||||
</Head>
|
||||
<div className="h-full dark:bg-black dark:text-white-60 bg-white relative z-0 text-black-60 grid grid-rows-[min-content,1fr]">
|
||||
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
|
||||
<Navbar />
|
||||
<div className="flex items-center gap-4 ml-auto mr-8">
|
||||
<VegaWalletConnectButton
|
||||
setConnectDialog={(open) => {
|
||||
store.setVegaWalletConnectDialog(open);
|
||||
}}
|
||||
setManageDialog={(open) => {
|
||||
store.setVegaWalletManageDialog(open);
|
||||
}}
|
||||
/>
|
||||
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
|
||||
</div>
|
||||
</div>
|
||||
<main data-testid={pageProps.page}>
|
||||
{/* @ts-ignore conflict between @types/react and nextjs internal types */}
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
<VegaConnectDialog
|
||||
connectors={Connectors}
|
||||
dialogOpen={store.vegaWalletConnectDialog}
|
||||
setDialogOpen={(open) =>
|
||||
store.setVegaWalletConnectDialog(open)
|
||||
}
|
||||
/>
|
||||
<VegaManageDialog
|
||||
dialogOpen={store.vegaWalletManageDialog}
|
||||
setDialogOpen={(open) =>
|
||||
store.setVegaWalletManageDialog(open)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<main data-testid={pageProps.page}>
|
||||
{/* @ts-ignore conflict between @types/react and nextjs internal types */}
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
<VegaConnectDialog
|
||||
connectors={Connectors}
|
||||
dialogOpen={vegaWallet.connect}
|
||||
setDialogOpen={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, connect: open }))
|
||||
}
|
||||
/>
|
||||
<VegaManageDialog
|
||||
dialogOpen={vegaWallet.manage}
|
||||
setDialogOpen={(open) =>
|
||||
setVegaWallet((x) => ({ ...x, manage: open }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</AppLoader>
|
||||
</VegaWalletProvider>
|
||||
</ApolloProvider>
|
||||
</ThemeContext.Provider>
|
||||
</AppLoader>
|
||||
</VegaWalletProvider>
|
||||
</ApolloProvider>
|
||||
</ThemeContext.Provider>
|
||||
</EnvironmentProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { LandingDialog } from '@vegaprotocol/market-list';
|
||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import MarketPage from './markets/[marketId].page';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import { useGlobalStore } from '../stores';
|
||||
import type { MarketsLanding } from './__generated__/MarketsLanding';
|
||||
|
||||
const MARKETS_QUERY = gql`
|
||||
@ -29,24 +30,33 @@ const marketList = ({ markets }: MarketsLanding) =>
|
||||
);
|
||||
|
||||
export function Index() {
|
||||
const { replace } = useRouter();
|
||||
// The default market selected in the platform behind the overlay
|
||||
// should be the oldest market that is currently trading in continuous mode(i.e. not in auction).
|
||||
const { data, error, loading } = useQuery<MarketsLanding>(MARKETS_QUERY);
|
||||
if (data && !error && !loading) {
|
||||
const marketId = marketList(data)[0]?.id;
|
||||
window.history.replaceState(
|
||||
data,
|
||||
'',
|
||||
marketId ? `/markets/${marketId}` : '/markets'
|
||||
);
|
||||
}
|
||||
const setLandingDialog = useGlobalStore((state) => state.setLandingDialog);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const marketId = marketList(data)[0]?.id;
|
||||
|
||||
// If a default market is found, go to it with the landing dialog open
|
||||
if (marketId) {
|
||||
setLandingDialog(true);
|
||||
replace(`/markets/${marketId}`);
|
||||
}
|
||||
// Fallback to the markets list page
|
||||
else {
|
||||
replace('/markets');
|
||||
}
|
||||
}
|
||||
}, [data, replace, setLandingDialog]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<LandingDialog />
|
||||
<AsyncRenderer data={data} error={error} loading={loading}>
|
||||
<MarketPage id={data && marketList(data)[0]?.id} />
|
||||
</AsyncRenderer>
|
||||
</>
|
||||
<AsyncRenderer data={data} loading={loading} error={error}>
|
||||
{/* Render a loading and error state but we will redirect if markets are found */}
|
||||
{null}
|
||||
</AsyncRenderer>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ import debounce from 'lodash/debounce';
|
||||
import { PageQueryContainer } from '../../components/page-query-container';
|
||||
import { TradeGrid, TradePanels } from './trade-grid';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { LandingDialog } from '@vegaprotocol/market-list';
|
||||
|
||||
// Top level page query
|
||||
const MARKET_QUERY = gql`
|
||||
@ -21,6 +23,7 @@ const MARKET_QUERY = gql`
|
||||
const MarketPage = ({ id }: { id?: string }) => {
|
||||
const { query } = useRouter();
|
||||
const { w } = useWindowSize();
|
||||
const store = useGlobalStore();
|
||||
|
||||
// Default to first marketId query item if found
|
||||
const marketId =
|
||||
@ -48,10 +51,18 @@ const MarketPage = ({ id }: { id?: string }) => {
|
||||
return <Splash>{t('Market not found')}</Splash>;
|
||||
}
|
||||
|
||||
return w > 960 ? (
|
||||
<TradeGrid market={market} />
|
||||
) : (
|
||||
<TradePanels market={market} />
|
||||
return (
|
||||
<>
|
||||
{w > 960 ? (
|
||||
<TradeGrid market={market} />
|
||||
) : (
|
||||
<TradePanels market={market} />
|
||||
)}
|
||||
<LandingDialog
|
||||
open={store.landingDialog}
|
||||
setOpen={(isOpen) => store.setLandingDialog(isOpen)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
@ -3,7 +3,7 @@ import { gql } from '@apollo/client';
|
||||
import { PageQueryContainer } from '../../../components/page-query-container';
|
||||
import type { DepositPage } from './__generated__/DepositPage';
|
||||
import { DepositManager } from '@vegaprotocol/deposits';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { t, useEnvironment } from '@vegaprotocol/react-helpers';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { ASSET_FRAGMENT } from '../../../lib/query-fragments';
|
||||
|
||||
@ -28,6 +28,8 @@ export const DepositContainer = ({
|
||||
ethereumConfig,
|
||||
assetId,
|
||||
}: DepositContainerProps) => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
|
||||
return (
|
||||
<PageQueryContainer<DepositPage>
|
||||
query={DEPOSIT_PAGE_QUERY}
|
||||
@ -46,6 +48,7 @@ export const DepositContainer = ({
|
||||
requiredConfirmations={ethereumConfig.confirmations}
|
||||
assets={data.assets}
|
||||
initialAssetId={assetId}
|
||||
isFaucetable={VEGA_ENV !== 'MAINNET'}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Web3Container } from '../../../components/web3-container';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useMemo } from 'react';
|
||||
import { WithdrawPageContainer } from './withdraw-page-container';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { VegaWalletContainer } from '../../../components/vega-wallet-container';
|
||||
import { Web3Container } from '../../../components/web3-container';
|
||||
|
||||
const Withdraw = () => {
|
||||
const { query } = useRouter();
|
||||
@ -21,14 +22,16 @@ const Withdraw = () => {
|
||||
}, [query]);
|
||||
|
||||
return (
|
||||
<Web3Container
|
||||
render={() => (
|
||||
<div className="max-w-[420px] p-24 mx-auto">
|
||||
<h1 className="text-h3 mb-12">{t('Withdraw')}</h1>
|
||||
<WithdrawPageContainer assetId={assetId} />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<VegaWalletContainer>
|
||||
<Web3Container
|
||||
render={() => (
|
||||
<div className="max-w-[420px] p-24 mx-auto">
|
||||
<h1 className="text-h3 mb-12">{t('Withdraw')}</h1>
|
||||
<WithdrawPageContainer assetId={assetId} />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -47,14 +47,6 @@ export const WithdrawPageContainer = ({
|
||||
}: WithdrawPageContainerProps) => {
|
||||
const { keypair } = useVegaWallet();
|
||||
|
||||
if (!keypair) {
|
||||
return (
|
||||
<p data-testid="connect-vega-wallet-text">
|
||||
{t('Please connect your Vega wallet')}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageQueryContainer<WithdrawPageQuery, WithdrawPageQueryVariables>
|
||||
query={WITHDRAW_PAGE_QUERY}
|
||||
|
@ -1,30 +1,26 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { AnchorButton, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { AnchorButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { VegaWalletContainer } from '../../../components/vega-wallet-container';
|
||||
import { Web3Container } from '../../../components/web3-container';
|
||||
import { WithdrawalsPageContainer } from './withdrawals-page-container';
|
||||
|
||||
const Withdrawals = () => {
|
||||
const { keypair } = useVegaWallet();
|
||||
|
||||
if (!keypair) {
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Web3Container
|
||||
render={() => (
|
||||
<div className="h-full grid grid grid-rows-[min-content,1fr]">
|
||||
<header className="flex justify-between p-24">
|
||||
<h1 className="text-h3">{t('Withdrawals')}</h1>
|
||||
<AnchorButton href="/portfolio/withdraw">
|
||||
{t('Start withdrawal')}
|
||||
</AnchorButton>
|
||||
</header>
|
||||
<WithdrawalsPageContainer />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<VegaWalletContainer>
|
||||
<Web3Container
|
||||
render={() => (
|
||||
<div className="h-full grid grid grid-rows-[min-content,1fr]">
|
||||
<header className="flex justify-between p-24">
|
||||
<h1 className="text-h3">{t('Withdrawals')}</h1>
|
||||
<AnchorButton href="/portfolio/withdraw">
|
||||
{t('Start withdrawal')}
|
||||
</AnchorButton>
|
||||
</header>
|
||||
<WithdrawalsPageContainer />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
26
apps/trading/stores/global.ts
Normal file
26
apps/trading/stores/global.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { SetState } from 'zustand';
|
||||
import create from 'zustand';
|
||||
|
||||
interface GlobalStore {
|
||||
vegaWalletConnectDialog: boolean;
|
||||
setVegaWalletConnectDialog: (isOpen: boolean) => void;
|
||||
vegaWalletManageDialog: boolean;
|
||||
setVegaWalletManageDialog: (isOpen: boolean) => void;
|
||||
landingDialog: boolean;
|
||||
setLandingDialog: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export const useGlobalStore = create((set: SetState<GlobalStore>) => ({
|
||||
vegaWalletConnectDialog: false,
|
||||
setVegaWalletConnectDialog: (isOpen: boolean) => {
|
||||
set({ vegaWalletConnectDialog: isOpen });
|
||||
},
|
||||
vegaWalletManageDialog: false,
|
||||
setVegaWalletManageDialog: (isOpen: boolean) => {
|
||||
set({ vegaWalletManageDialog: isOpen });
|
||||
},
|
||||
landingDialog: false,
|
||||
setLandingDialog: (isOpen: boolean) => {
|
||||
set({ landingDialog: isOpen });
|
||||
},
|
||||
}));
|
1
apps/trading/stores/index.ts
Normal file
1
apps/trading/stores/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './global';
|
@ -9,7 +9,6 @@
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"incremental": true
|
||||
},
|
||||
|
@ -8,7 +8,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
|
@ -8,7 +8,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
|
@ -17,7 +17,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
|
@ -72,6 +72,12 @@ export interface DealTicketQuery_market {
|
||||
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
|
||||
*/
|
||||
decimalPlaces: number;
|
||||
/**
|
||||
* positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
|
||||
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
|
||||
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
|
||||
*/
|
||||
positionDecimalPlaces: number;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
|
33
libs/deal-ticket/src/components/deal-ticket-amount.tsx
Normal file
33
libs/deal-ticket/src/components/deal-ticket-amount.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import type { UseFormRegister } from 'react-hook-form';
|
||||
import { OrderType } from '@vegaprotocol/wallet';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import { DealTicketMarketAmount } from './deal-ticket-market-amount';
|
||||
import { DealTicketLimitAmount } from './deal-ticket-limit-amount';
|
||||
|
||||
export interface DealTicketAmountProps {
|
||||
orderType: OrderType;
|
||||
step: number;
|
||||
register: UseFormRegister<Order>;
|
||||
quoteName: string;
|
||||
price?: string;
|
||||
}
|
||||
|
||||
const getAmountComponent = (type: OrderType) => {
|
||||
switch (type) {
|
||||
case OrderType.Market:
|
||||
return DealTicketMarketAmount;
|
||||
case OrderType.Limit:
|
||||
return DealTicketLimitAmount;
|
||||
default: {
|
||||
throw new Error('Invalid ticket type');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const DealTicketAmount = ({
|
||||
orderType,
|
||||
...props
|
||||
}: DealTicketAmountProps) => {
|
||||
const AmountComponent = getAmountComponent(orderType);
|
||||
return <AmountComponent {...props} />;
|
||||
};
|
@ -4,7 +4,7 @@ import { DealTicketManager } from './deal-ticket-manager';
|
||||
import type {
|
||||
DealTicketQuery,
|
||||
DealTicketQuery_market,
|
||||
} from './__generated__/DealTicketQuery';
|
||||
} from '../__generated__/DealTicketQuery';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const DEAL_TICKET_QUERY = gql`
|
||||
@ -12,6 +12,7 @@ const DEAL_TICKET_QUERY = gql`
|
||||
market(id: $marketId) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
state
|
||||
tradingMode
|
||||
tradableInstrument {
|
@ -1,30 +1,32 @@
|
||||
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
|
||||
import { validateSize } from '../utils/validate-size';
|
||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||
|
||||
export interface DealTicketLimitFormProps {
|
||||
quoteName: string;
|
||||
price?: string;
|
||||
size: string;
|
||||
onSizeChange: (size: string) => void;
|
||||
onPriceChange: (price: string) => void;
|
||||
}
|
||||
export type DealTicketLimitAmountProps = Omit<
|
||||
DealTicketAmountProps,
|
||||
'orderType'
|
||||
>;
|
||||
|
||||
export const DealTicketLimitForm = ({
|
||||
size,
|
||||
price,
|
||||
onSizeChange,
|
||||
onPriceChange,
|
||||
export const DealTicketLimitAmount = ({
|
||||
register,
|
||||
step,
|
||||
quoteName,
|
||||
}: DealTicketLimitFormProps) => {
|
||||
}: DealTicketLimitAmountProps) => {
|
||||
return (
|
||||
<div className="flex items-center gap-8">
|
||||
<div className="flex-1">
|
||||
<FormGroup label="Amount">
|
||||
<Input
|
||||
value={size}
|
||||
onChange={(e) => onSizeChange(e.target.value)}
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={step}
|
||||
min={step}
|
||||
data-testid="order-size"
|
||||
{...register('size', {
|
||||
required: true,
|
||||
min: step,
|
||||
validate: validateSize(step),
|
||||
})}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
@ -32,11 +34,12 @@ export const DealTicketLimitForm = ({
|
||||
<div className="flex-1">
|
||||
<FormGroup label={`Price (${quoteName})`} labelAlign="right">
|
||||
<Input
|
||||
value={price}
|
||||
onChange={(e) => onPriceChange(e.target.value)}
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={step}
|
||||
defaultValue={0}
|
||||
data-testid="order-price"
|
||||
{...register('price', { required: true, min: 0 })}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
@ -4,9 +4,9 @@ import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
import { useOrderSubmit } from './use-order-submit';
|
||||
import { OrderDialog } from './order-dialog';
|
||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
||||
import { useOrderSubmit } from '../hooks/use-order-submit';
|
||||
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||
|
||||
export interface DealTicketManagerProps {
|
||||
market: DealTicketQuery_market;
|
@ -1,28 +1,33 @@
|
||||
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
|
||||
import { validateSize } from '../utils/validate-size';
|
||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||
|
||||
export interface DealTicketMarketFormProps {
|
||||
quoteName?: string;
|
||||
price?: string;
|
||||
size: string;
|
||||
onSizeChange: (size: string) => void;
|
||||
}
|
||||
export type DealTicketMarketAmountProps = Omit<
|
||||
DealTicketAmountProps,
|
||||
'orderType'
|
||||
>;
|
||||
|
||||
export const DealTicketMarketForm = ({
|
||||
size,
|
||||
onSizeChange,
|
||||
export const DealTicketMarketAmount = ({
|
||||
register,
|
||||
price,
|
||||
step,
|
||||
quoteName,
|
||||
}: DealTicketMarketFormProps) => {
|
||||
}: DealTicketMarketAmountProps) => {
|
||||
return (
|
||||
<div className="flex items-center gap-8">
|
||||
<div className="flex-1">
|
||||
<FormGroup label="Amount">
|
||||
<Input
|
||||
value={size}
|
||||
onChange={(e) => onSizeChange(e.target.value)}
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={step}
|
||||
min={step}
|
||||
data-testid="order-size"
|
||||
{...register('size', {
|
||||
required: true,
|
||||
min: step,
|
||||
validate: validateSize(step),
|
||||
})}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
@ -4,22 +4,17 @@ import {
|
||||
OrderType,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { fireEvent, render, screen, act } from '@testing-library/react';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
import type { Order } from './use-order-state';
|
||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
||||
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
|
||||
const order: Order = {
|
||||
type: OrderType.Market,
|
||||
size: '100',
|
||||
timeInForce: OrderTimeInForce.FOK,
|
||||
side: null,
|
||||
};
|
||||
const market: DealTicketQuery_market = {
|
||||
__typename: 'Market',
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 1,
|
||||
tradingMode: MarketTradingMode.Continuous,
|
||||
state: MarketState.Active,
|
||||
tradableInstrument: {
|
||||
@ -43,7 +38,7 @@ const market: DealTicketQuery_market = {
|
||||
const submit = jest.fn();
|
||||
const transactionStatus = 'default';
|
||||
|
||||
function generateJsx() {
|
||||
function generateJsx(order?: Order) {
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
<VegaWalletContext.Provider value={{} as any}>
|
||||
@ -57,21 +52,23 @@ function generateJsx() {
|
||||
);
|
||||
}
|
||||
|
||||
it('Deal ticket defaults', () => {
|
||||
it('Displays ticket defaults', () => {
|
||||
render(generateJsx());
|
||||
|
||||
// Assert defaults are used
|
||||
expect(
|
||||
screen.getByTestId(`order-type-${order.type}-selected`)
|
||||
screen.getByTestId(`order-type-${OrderType.Market}-selected`)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_BUY-selected')
|
||||
).not.toBeInTheDocument();
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_SELL-selected')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue(order.size);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(order.timeInForce);
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
|
||||
String(1 / Math.pow(10, market.positionDecimalPlaces))
|
||||
);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.IOC);
|
||||
|
||||
// Assert last price is shown
|
||||
expect(screen.getByTestId('last-price')).toHaveTextContent(
|
||||
@ -82,18 +79,18 @@ it('Deal ticket defaults', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('Can edit deal ticket', () => {
|
||||
it('Can edit deal ticket', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
// Asssert changing values
|
||||
fireEvent.click(screen.getByTestId('order-side-SIDE_BUY'));
|
||||
expect(
|
||||
screen.getByTestId('order-side-SIDE_BUY-selected')
|
||||
).toBeInTheDocument();
|
||||
// BUY is selected by default
|
||||
screen.getByTestId('order-side-SIDE_BUY-selected');
|
||||
|
||||
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'), {
|
126
libs/deal-ticket/src/components/deal-ticket.tsx
Normal file
126
libs/deal-ticket/src/components/deal-ticket.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { OrderType, OrderTimeInForce } from '@vegaprotocol/wallet';
|
||||
import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { TypeSelector } from './type-selector';
|
||||
import { SideSelector } from './side-selector';
|
||||
import { DealTicketAmount } from './deal-ticket-amount';
|
||||
import { TimeInForceSelector } from './time-in-force-selector';
|
||||
import { ExpirySelector } from './expiry-selector';
|
||||
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import { getDefaultOrder } from '../utils/get-default-order';
|
||||
import { useOrderValidation } from '../hooks/use-order-validation';
|
||||
|
||||
export type TransactionStatus = 'default' | 'pending';
|
||||
|
||||
export interface DealTicketProps {
|
||||
market: DealTicketQuery_market;
|
||||
submit: (order: Order) => void;
|
||||
transactionStatus: TransactionStatus;
|
||||
defaultOrder?: Order;
|
||||
}
|
||||
|
||||
export const DealTicket = ({
|
||||
market,
|
||||
submit,
|
||||
transactionStatus,
|
||||
}: DealTicketProps) => {
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<Order>({
|
||||
mode: 'onChange',
|
||||
defaultValues: getDefaultOrder(market),
|
||||
});
|
||||
|
||||
const step = toDecimal(market.positionDecimalPlaces);
|
||||
const orderType = watch('type');
|
||||
const orderTimeInForce = watch('timeInForce');
|
||||
const invalidText = useOrderValidation({
|
||||
step,
|
||||
market,
|
||||
orderType,
|
||||
orderTimeInForce,
|
||||
fieldErrors: errors,
|
||||
});
|
||||
const isDisabled = transactionStatus === 'pending' || Boolean(invalidText);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(order: Order) => {
|
||||
if (!isDisabled && !invalidText) {
|
||||
submit(order);
|
||||
}
|
||||
},
|
||||
[isDisabled, invalidText, submit]
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="px-4 py-8" noValidate>
|
||||
<Controller
|
||||
name="type"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TypeSelector value={field.value} onSelect={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="side"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SideSelector value={field.value} onSelect={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
<DealTicketAmount
|
||||
orderType={orderType}
|
||||
step={0.02}
|
||||
register={register}
|
||||
price={
|
||||
market.depth.lastTrade
|
||||
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
||||
: undefined
|
||||
}
|
||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||
/>
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TimeInForceSelector
|
||||
value={field.value}
|
||||
orderType={orderType}
|
||||
onSelect={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{orderType === OrderType.Limit &&
|
||||
orderTimeInForce === OrderTimeInForce.GTT && (
|
||||
<Controller
|
||||
name="expiration"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<ExpirySelector value={field.value} onSelect={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
className="w-full mb-8"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={isDisabled}
|
||||
data-testid="place-order"
|
||||
>
|
||||
{transactionStatus === 'pending' ? t('Pending...') : t('Place order')}
|
||||
</Button>
|
||||
{invalidText && (
|
||||
<InputError className="mb-8" data-testid="dealticket-error-message">
|
||||
{invalidText}
|
||||
</InputError>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
};
|
@ -1,14 +1,13 @@
|
||||
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
|
||||
import type { Order } from './use-order-state';
|
||||
import { formatForInput } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface ExpirySelectorProps {
|
||||
order: Order;
|
||||
value?: Date;
|
||||
onSelect: (expiration: Date | null) => void;
|
||||
}
|
||||
|
||||
export const ExpirySelector = ({ order, onSelect }: ExpirySelectorProps) => {
|
||||
const date = order.expiration ? new Date(order.expiration) : new Date();
|
||||
export const ExpirySelector = ({ value, onSelect }: ExpirySelectorProps) => {
|
||||
const date = value ? new Date(value) : new Date();
|
||||
const dateFormatted = formatForInput(date);
|
||||
const minDate = formatForInput(date);
|
||||
return (
|
@ -1,6 +1,6 @@
|
||||
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { OrderEvent_busEvents_event_Order } from './__generated__/OrderEvent';
|
||||
import type { OrderEvent_busEvents_event_Order } from '../__generated__/OrderEvent';
|
||||
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
|
||||
import type { VegaTxState } from '@vegaprotocol/wallet';
|
||||
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
@ -1,14 +1,13 @@
|
||||
import { FormGroup } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderSide } from '@vegaprotocol/wallet';
|
||||
import { Toggle } from '@vegaprotocol/ui-toolkit';
|
||||
import type { Order } from './use-order-state';
|
||||
|
||||
interface SideSelectorProps {
|
||||
order: Order;
|
||||
value: OrderSide;
|
||||
onSelect: (side: OrderSide) => void;
|
||||
}
|
||||
|
||||
export const SideSelector = ({ order, onSelect }: SideSelectorProps) => {
|
||||
export const SideSelector = ({ value, onSelect }: SideSelectorProps) => {
|
||||
const toggles = Object.entries(OrderSide).map(([label, value]) => ({
|
||||
label,
|
||||
value,
|
||||
@ -19,7 +18,7 @@ export const SideSelector = ({ order, onSelect }: SideSelectorProps) => {
|
||||
<Toggle
|
||||
name="order-side"
|
||||
toggles={toggles}
|
||||
checkedValue={order.side}
|
||||
checkedValue={value}
|
||||
onChange={(e) => onSelect(e.target.value as OrderSide)}
|
||||
/>
|
||||
</FormGroup>
|
@ -1,28 +1,30 @@
|
||||
import { FormGroup, Select } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import type { Order } from './use-order-state';
|
||||
|
||||
interface TimeInForceSelectorProps {
|
||||
order: Order;
|
||||
value: OrderTimeInForce;
|
||||
orderType: OrderType;
|
||||
onSelect: (tif: OrderTimeInForce) => void;
|
||||
}
|
||||
|
||||
export const TimeInForceSelector = ({
|
||||
order,
|
||||
value,
|
||||
orderType,
|
||||
onSelect,
|
||||
}: TimeInForceSelectorProps) => {
|
||||
const options =
|
||||
order.type === OrderType.Limit
|
||||
orderType === OrderType.Limit
|
||||
? Object.entries(OrderTimeInForce)
|
||||
: Object.entries(OrderTimeInForce).filter(
|
||||
([_, value]) =>
|
||||
value === OrderTimeInForce.FOK || value === OrderTimeInForce.IOC
|
||||
([_, timeInForce]) =>
|
||||
timeInForce === OrderTimeInForce.FOK ||
|
||||
timeInForce === OrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup label="Time in force">
|
||||
<Select
|
||||
value={order.timeInForce}
|
||||
value={value}
|
||||
onChange={(e) => onSelect(e.target.value as OrderTimeInForce)}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
@ -1,25 +1,24 @@
|
||||
import { FormGroup } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderType } from '@vegaprotocol/wallet';
|
||||
import { Toggle } from '@vegaprotocol/ui-toolkit';
|
||||
import type { Order } from './use-order-state';
|
||||
|
||||
interface TypeSelectorProps {
|
||||
order: Order;
|
||||
value: OrderType;
|
||||
onSelect: (type: OrderType) => void;
|
||||
}
|
||||
|
||||
export const TypeSelector = ({ order, onSelect }: TypeSelectorProps) => {
|
||||
const toggles = Object.entries(OrderType).map(([label, value]) => ({
|
||||
label,
|
||||
value,
|
||||
}));
|
||||
const toggles = Object.entries(OrderType).map(([label, value]) => ({
|
||||
label,
|
||||
value,
|
||||
}));
|
||||
|
||||
export const TypeSelector = ({ value, onSelect }: TypeSelectorProps) => {
|
||||
return (
|
||||
<FormGroup label="Order type">
|
||||
<Toggle
|
||||
name="order-type"
|
||||
toggles={toggles}
|
||||
checkedValue={order.type}
|
||||
checkedValue={value}
|
||||
onChange={(e) => onSelect(e.target.value as OrderType)}
|
||||
/>
|
||||
</FormGroup>
|
@ -1,57 +0,0 @@
|
||||
import { OrderTimeInForce } from '@vegaprotocol/wallet';
|
||||
import type { TransactionStatus } from './deal-ticket';
|
||||
import { ExpirySelector } from './expiry-selector';
|
||||
import { SideSelector } from './side-selector';
|
||||
import { SubmitButton } from './submit-button';
|
||||
import { DealTicketLimitForm } from './deal-ticket-limit-form';
|
||||
import { TimeInForceSelector } from './time-in-force-selector';
|
||||
import { TypeSelector } from './type-selector';
|
||||
import type { Order } from './use-order-state';
|
||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
||||
|
||||
interface DealTicketLimitProps {
|
||||
order: Order;
|
||||
updateOrder: (order: Partial<Order>) => void;
|
||||
transactionStatus: TransactionStatus;
|
||||
market: DealTicketQuery_market;
|
||||
}
|
||||
|
||||
export const DealTicketLimit = ({
|
||||
order,
|
||||
updateOrder,
|
||||
transactionStatus,
|
||||
market,
|
||||
}: DealTicketLimitProps) => {
|
||||
return (
|
||||
<>
|
||||
<TypeSelector order={order} onSelect={(type) => updateOrder({ type })} />
|
||||
<SideSelector order={order} onSelect={(side) => updateOrder({ side })} />
|
||||
<DealTicketLimitForm
|
||||
price={order.price}
|
||||
size={order.size}
|
||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||
onSizeChange={(size) => updateOrder({ size })}
|
||||
onPriceChange={(price) => updateOrder({ price })}
|
||||
/>
|
||||
<TimeInForceSelector
|
||||
order={order}
|
||||
onSelect={(timeInForce) => updateOrder({ timeInForce })}
|
||||
/>
|
||||
{order.timeInForce === OrderTimeInForce.GTT && (
|
||||
<ExpirySelector
|
||||
order={order}
|
||||
onSelect={(date) => {
|
||||
if (date) {
|
||||
updateOrder({ expiration: date });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<SubmitButton
|
||||
transactionStatus={transactionStatus}
|
||||
market={market}
|
||||
order={order}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,49 +0,0 @@
|
||||
import type { TransactionStatus } from './deal-ticket';
|
||||
import { SideSelector } from './side-selector';
|
||||
import { DealTicketMarketForm } from './deal-ticket-market-form';
|
||||
import { SubmitButton } from './submit-button';
|
||||
import { TimeInForceSelector } from './time-in-force-selector';
|
||||
import { TypeSelector } from './type-selector';
|
||||
import type { Order } from './use-order-state';
|
||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface DealTicketMarketProps {
|
||||
order: Order;
|
||||
updateOrder: (order: Partial<Order>) => void;
|
||||
transactionStatus: TransactionStatus;
|
||||
market: DealTicketQuery_market;
|
||||
}
|
||||
|
||||
export const DealTicketMarket = ({
|
||||
order,
|
||||
updateOrder,
|
||||
transactionStatus,
|
||||
market,
|
||||
}: DealTicketMarketProps) => {
|
||||
return (
|
||||
<>
|
||||
<TypeSelector order={order} onSelect={(type) => updateOrder({ type })} />
|
||||
<SideSelector order={order} onSelect={(side) => updateOrder({ side })} />
|
||||
<DealTicketMarketForm
|
||||
size={order.size}
|
||||
onSizeChange={(size) => updateOrder({ size })}
|
||||
price={
|
||||
market.depth.lastTrade
|
||||
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
||||
: undefined
|
||||
}
|
||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||
/>
|
||||
<TimeInForceSelector
|
||||
order={order}
|
||||
onSelect={(timeInForce) => updateOrder({ timeInForce })}
|
||||
/>
|
||||
<SubmitButton
|
||||
transactionStatus={transactionStatus}
|
||||
market={market}
|
||||
order={order}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,67 +0,0 @@
|
||||
import type { FormEvent } from 'react';
|
||||
import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import type { Order } from './use-order-state';
|
||||
import { useOrderState } from './use-order-state';
|
||||
import { DealTicketMarket } from './deal-ticket-market';
|
||||
import { DealTicketLimit } from './deal-ticket-limit';
|
||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
||||
|
||||
const DEFAULT_ORDER: Order = {
|
||||
type: OrderType.Market,
|
||||
side: OrderSide.Buy,
|
||||
size: '1',
|
||||
timeInForce: OrderTimeInForce.IOC,
|
||||
};
|
||||
|
||||
export type TransactionStatus = 'default' | 'pending';
|
||||
|
||||
export interface DealTicketProps {
|
||||
market: DealTicketQuery_market;
|
||||
submit: (order: Order) => void;
|
||||
transactionStatus: TransactionStatus;
|
||||
defaultOrder?: Order;
|
||||
}
|
||||
|
||||
export const DealTicket = ({
|
||||
market,
|
||||
submit,
|
||||
transactionStatus,
|
||||
defaultOrder = DEFAULT_ORDER,
|
||||
}: DealTicketProps) => {
|
||||
const [order, updateOrder] = useOrderState(defaultOrder);
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
submit(order);
|
||||
};
|
||||
|
||||
let ticket = null;
|
||||
|
||||
if (order.type === OrderType.Market) {
|
||||
ticket = (
|
||||
<DealTicketMarket
|
||||
order={order}
|
||||
updateOrder={updateOrder}
|
||||
transactionStatus={transactionStatus}
|
||||
market={market}
|
||||
/>
|
||||
);
|
||||
} else if (order.type === OrderType.Limit) {
|
||||
ticket = (
|
||||
<DealTicketLimit
|
||||
order={order}
|
||||
updateOrder={updateOrder}
|
||||
transactionStatus={transactionStatus}
|
||||
market={market}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
throw new Error('Invalid ticket type');
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="px-4 py-8">
|
||||
{ticket}
|
||||
</form>
|
||||
);
|
||||
};
|
@ -1,14 +1,42 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import type { Order } from './use-order-state';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import type {
|
||||
VegaKeyExtended,
|
||||
VegaWalletContextShape,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useOrderSubmit } from './use-order-submit';
|
||||
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||
|
||||
const defaultMarket: DealTicketQuery_market = {
|
||||
__typename: 'Market',
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 1,
|
||||
tradingMode: MarketTradingMode.Continuous,
|
||||
state: MarketState.Active,
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'quote-name',
|
||||
},
|
||||
},
|
||||
},
|
||||
depth: {
|
||||
__typename: 'MarketDepth',
|
||||
lastTrade: {
|
||||
__typename: 'Trade',
|
||||
price: '100',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const defaultWalletContext = {
|
||||
keypair: null,
|
||||
@ -22,7 +50,7 @@ const defaultWalletContext = {
|
||||
|
||||
function setup(
|
||||
context?: Partial<VegaWalletContextShape>,
|
||||
market = { id: 'market-id', decimalPlaces: 2 }
|
||||
market = defaultMarket
|
||||
) {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<MockedProvider>
|
||||
@ -111,20 +139,16 @@ it('Should submit a correctly formatted order', async () => {
|
||||
const keypair = {
|
||||
pub: '0x123',
|
||||
} as VegaKeyExtended;
|
||||
const market = {
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
};
|
||||
const { result } = setup(
|
||||
{
|
||||
sendTx: mockSendTx,
|
||||
keypairs: [keypair],
|
||||
keypair,
|
||||
},
|
||||
market
|
||||
defaultMarket
|
||||
);
|
||||
|
||||
const order = {
|
||||
const order: Order = {
|
||||
type: OrderType.Limit,
|
||||
size: '10',
|
||||
timeInForce: OrderTimeInForce.GTT,
|
||||
@ -141,12 +165,12 @@ it('Should submit a correctly formatted order', async () => {
|
||||
propagate: true,
|
||||
orderSubmission: {
|
||||
type: OrderType.Limit,
|
||||
marketId: market.id, // Market provided from hook arugment
|
||||
size: '10',
|
||||
marketId: defaultMarket.id, // Market provided from hook arugment
|
||||
size: '100', // size adjusted based on positionDecimalPlaces
|
||||
side: OrderSide.Buy,
|
||||
timeInForce: OrderTimeInForce.GTT,
|
||||
price: '123456789', // Decimal removed
|
||||
expiresAt: order.expiration.getTime() + '000000', // Nanoseconds appened
|
||||
expiresAt: order.expiration?.getTime() + '000000', // Nanoseconds appened
|
||||
},
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { gql, useSubscription } from '@apollo/client';
|
||||
import type { Order } from './use-order-state';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import { OrderType, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { determineId, removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { useVegaTransaction } from '@vegaprotocol/wallet';
|
||||
@ -8,7 +8,8 @@ import type {
|
||||
OrderEvent,
|
||||
OrderEventVariables,
|
||||
OrderEvent_busEvents_event_Order,
|
||||
} from './__generated__/OrderEvent';
|
||||
} from '../__generated__/OrderEvent';
|
||||
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||
|
||||
const ORDER_EVENT_SUB = gql`
|
||||
subscription OrderEvent($partyId: ID!) {
|
||||
@ -35,12 +36,7 @@ const ORDER_EVENT_SUB = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
interface UseOrderSubmitMarket {
|
||||
id: string;
|
||||
decimalPlaces: number;
|
||||
}
|
||||
|
||||
export const useOrderSubmit = (market: UseOrderSubmitMarket) => {
|
||||
export const useOrderSubmit = (market: DealTicketQuery_market) => {
|
||||
const { keypair } = useVegaWallet();
|
||||
const { send, transaction, reset: resetTransaction } = useVegaTransaction();
|
||||
const [id, setId] = useState('');
|
||||
@ -97,7 +93,7 @@ export const useOrderSubmit = (market: UseOrderSubmitMarket) => {
|
||||
order.type === OrderType.Limit && order.price
|
||||
? removeDecimal(order.price, market.decimalPlaces)
|
||||
: undefined,
|
||||
size: order.size,
|
||||
size: removeDecimal(order.size, market.positionDecimalPlaces),
|
||||
type: order.type,
|
||||
side: order.side,
|
||||
timeInForce: order.timeInForce,
|
196
libs/deal-ticket/src/hooks/use-order-validation.spec.tsx
Normal file
196
libs/deal-ticket/src/hooks/use-order-validation.spec.tsx
Normal file
@ -0,0 +1,196 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import {
|
||||
OrderTimeInForce,
|
||||
OrderType,
|
||||
useVegaWallet,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import type {
|
||||
VegaWalletContextShape,
|
||||
VegaKeyExtended,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { ValidationProps } from './use-order-validation';
|
||||
import { useOrderValidation } from './use-order-validation';
|
||||
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
|
||||
|
||||
jest.mock('@vegaprotocol/wallet');
|
||||
|
||||
const market: DealTicketQuery_market = {
|
||||
__typename: 'Market',
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 1,
|
||||
tradingMode: MarketTradingMode.Continuous,
|
||||
state: MarketState.Active,
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'quote-name',
|
||||
},
|
||||
},
|
||||
},
|
||||
depth: {
|
||||
__typename: 'MarketDepth',
|
||||
lastTrade: {
|
||||
__typename: 'Trade',
|
||||
price: '100',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const defaultWalletContext = {
|
||||
keypair: {
|
||||
name: 'keypair0',
|
||||
tainted: false,
|
||||
pub: '111111__111111',
|
||||
} as VegaKeyExtended,
|
||||
keypairs: [],
|
||||
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
|
||||
connect: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
selectPublicKey: jest.fn(),
|
||||
connector: null,
|
||||
};
|
||||
|
||||
const defaultOrder = {
|
||||
market,
|
||||
step: 0.1,
|
||||
orderType: OrderType.Market,
|
||||
orderTimeInForce: OrderTimeInForce.FOK,
|
||||
};
|
||||
|
||||
const ERROR = {
|
||||
KEY_MISSING: 'No public key selected',
|
||||
KEY_TAINTED: 'Selected public key has been tainted',
|
||||
MARKET_SUSPENDED: 'Market is currently suspended',
|
||||
MARKET_INACTIVE: 'Market is no longer active',
|
||||
MARKET_WAITING: 'Market is not active yet',
|
||||
MARKET_CONTINUOUS_LIMIT:
|
||||
'Only limit orders are permitted when market is in auction',
|
||||
MARKET_COUNTINUOUS_TIF:
|
||||
'Only GTT, GTC and GFA are permitted when market is in auction',
|
||||
FIELD_SIZE_REQ: 'An amount needs to be provided',
|
||||
FIELD_SIZE_MIN: `The amount cannot be lower than "${defaultOrder.step}"`,
|
||||
FIELD_PRICE_REQ: 'A price needs to be provided',
|
||||
FIELD_PRICE_MIN: 'The price cannot be negative',
|
||||
FIELD_PRICE_STEP_NULL: 'No decimal amounts allowed for this order',
|
||||
FIELD_PRICE_STEP_DECIMAL: `The amount field only takes up to ${market.positionDecimalPlaces} decimals`,
|
||||
};
|
||||
|
||||
function setup(
|
||||
props?: Partial<ValidationProps>,
|
||||
context?: Partial<VegaWalletContextShape>
|
||||
) {
|
||||
const mockUseVegaWallet = useVegaWallet as jest.Mock;
|
||||
mockUseVegaWallet.mockReturnValue({ ...defaultWalletContext, context });
|
||||
return renderHook(() => useOrderValidation({ ...defaultOrder, ...props }));
|
||||
}
|
||||
|
||||
it('Returns empty string when given valid data', () => {
|
||||
const { result } = setup();
|
||||
expect(result.current).toEqual('');
|
||||
});
|
||||
|
||||
it('Returns an error message when no keypair found', async () => {
|
||||
const { result } = setup(defaultOrder, { keypair: null });
|
||||
expect(result.current).toEqual('');
|
||||
});
|
||||
|
||||
it('Returns an error message when the keypair is tainted', async () => {
|
||||
const { result } = setup(defaultOrder, {
|
||||
keypair: { ...defaultWalletContext.keypair, tainted: true },
|
||||
});
|
||||
expect(result.current).toEqual('');
|
||||
});
|
||||
|
||||
it.each`
|
||||
state | errorMessage
|
||||
${MarketState.Cancelled} | ${ERROR.MARKET_INACTIVE}
|
||||
${MarketState.Closed} | ${ERROR.MARKET_INACTIVE}
|
||||
${MarketState.Rejected} | ${ERROR.MARKET_INACTIVE}
|
||||
${MarketState.Settled} | ${ERROR.MARKET_INACTIVE}
|
||||
${MarketState.TradingTerminated} | ${ERROR.MARKET_INACTIVE}
|
||||
${MarketState.Suspended} | ${ERROR.MARKET_SUSPENDED}
|
||||
${MarketState.Pending} | ${ERROR.MARKET_WAITING}
|
||||
${MarketState.Proposed} | ${ERROR.MARKET_WAITING}
|
||||
`(
|
||||
'Returns an error message for "$marketState" market',
|
||||
async ({ state, errorMessage }) => {
|
||||
const { result } = setup({ market: { ...defaultOrder.market, state } });
|
||||
expect(result.current).toEqual(errorMessage);
|
||||
}
|
||||
);
|
||||
|
||||
it.each`
|
||||
tradingMode | errorMessage
|
||||
${MarketTradingMode.BatchAuction} | ${ERROR.MARKET_CONTINUOUS_LIMIT}
|
||||
${MarketTradingMode.MonitoringAuction} | ${ERROR.MARKET_CONTINUOUS_LIMIT}
|
||||
${MarketTradingMode.OpeningAuction} | ${ERROR.MARKET_CONTINUOUS_LIMIT}
|
||||
`(
|
||||
'Returns an error message when trying to submit a non-limit order for a "$tradingMode" market',
|
||||
async ({ tradingMode, errorMessage }) => {
|
||||
const { result } = setup({
|
||||
market: { ...defaultOrder.market, tradingMode },
|
||||
orderType: OrderType.Market,
|
||||
});
|
||||
expect(result.current).toEqual(errorMessage);
|
||||
}
|
||||
);
|
||||
|
||||
it.each`
|
||||
tradingMode | orderTimeInForce | errorMessage
|
||||
${MarketTradingMode.BatchAuction} | ${OrderTimeInForce.FOK} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.MonitoringAuction} | ${OrderTimeInForce.FOK} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.OpeningAuction} | ${OrderTimeInForce.FOK} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.BatchAuction} | ${OrderTimeInForce.IOC} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.MonitoringAuction} | ${OrderTimeInForce.IOC} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.OpeningAuction} | ${OrderTimeInForce.IOC} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.BatchAuction} | ${OrderTimeInForce.GFN} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.MonitoringAuction} | ${OrderTimeInForce.GFN} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.OpeningAuction} | ${OrderTimeInForce.GFN} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
`(
|
||||
'Returns an error message when submitting a limit order with a "$orderTimeInForce" value to a "$tradingMode" market',
|
||||
async ({ tradingMode, orderTimeInForce, errorMessage }) => {
|
||||
const { result } = setup({
|
||||
market: { ...defaultOrder.market, tradingMode },
|
||||
orderType: OrderType.Limit,
|
||||
orderTimeInForce,
|
||||
});
|
||||
expect(result.current).toEqual(errorMessage);
|
||||
}
|
||||
);
|
||||
|
||||
it.each`
|
||||
fieldName | errorType | errorMessage
|
||||
${'size'} | ${'required'} | ${ERROR.FIELD_SIZE_REQ}
|
||||
${'size'} | ${'min'} | ${ERROR.FIELD_SIZE_MIN}
|
||||
${'price'} | ${'required'} | ${ERROR.FIELD_PRICE_REQ}
|
||||
${'price'} | ${'min'} | ${ERROR.FIELD_PRICE_MIN}
|
||||
`(
|
||||
'Returns an error message when the order $fieldName "$errorType" validation fails',
|
||||
async ({ fieldName, errorType, errorMessage }) => {
|
||||
const { result } = setup({
|
||||
fieldErrors: { [fieldName]: { type: errorType } },
|
||||
});
|
||||
expect(result.current).toEqual(errorMessage);
|
||||
}
|
||||
);
|
||||
|
||||
it('Returns an error message when the order size incorrectly has decimal values', async () => {
|
||||
const { result } = setup({
|
||||
market: { ...market, positionDecimalPlaces: 0 },
|
||||
fieldErrors: { size: { type: 'validate', message: ERROR_SIZE_DECIMAL } },
|
||||
});
|
||||
expect(result.current).toEqual(ERROR.FIELD_PRICE_STEP_NULL);
|
||||
});
|
||||
|
||||
it('Returns an error message when the order size has more decimals then allowed', async () => {
|
||||
const { result } = setup({
|
||||
fieldErrors: { size: { type: 'validate', message: ERROR_SIZE_DECIMAL } },
|
||||
});
|
||||
expect(result.current).toEqual(ERROR.FIELD_PRICE_STEP_DECIMAL);
|
||||
});
|
114
libs/deal-ticket/src/hooks/use-order-validation.tsx
Normal file
114
libs/deal-ticket/src/hooks/use-order-validation.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
import type { FieldErrors } from 'react-hook-form';
|
||||
import { useMemo } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
useVegaWallet,
|
||||
OrderTimeInForce,
|
||||
OrderType,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
|
||||
|
||||
export type ValidationProps = {
|
||||
step: number;
|
||||
market: DealTicketQuery_market;
|
||||
orderType: OrderType;
|
||||
orderTimeInForce: OrderTimeInForce;
|
||||
fieldErrors?: FieldErrors<Order>;
|
||||
};
|
||||
|
||||
export const useOrderValidation = ({
|
||||
step,
|
||||
market,
|
||||
fieldErrors = {},
|
||||
orderType,
|
||||
orderTimeInForce,
|
||||
}: ValidationProps) => {
|
||||
const { keypair } = useVegaWallet();
|
||||
|
||||
const invalidText = useMemo(() => {
|
||||
if (!keypair) {
|
||||
return t('No public key selected');
|
||||
}
|
||||
|
||||
if (keypair.tainted) {
|
||||
return t('Selected public key has been tainted');
|
||||
}
|
||||
|
||||
if (market.state !== MarketState.Active) {
|
||||
if (market.state === MarketState.Suspended) {
|
||||
return t('Market is currently suspended');
|
||||
}
|
||||
|
||||
if (
|
||||
market.state === MarketState.Proposed ||
|
||||
market.state === MarketState.Pending
|
||||
) {
|
||||
return t('Market is not active yet');
|
||||
}
|
||||
|
||||
return t('Market is no longer active');
|
||||
}
|
||||
|
||||
if (market.tradingMode !== MarketTradingMode.Continuous) {
|
||||
if (orderType !== OrderType.Limit) {
|
||||
return t('Only limit orders are permitted when market is in auction');
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
OrderTimeInForce.FOK,
|
||||
OrderTimeInForce.IOC,
|
||||
OrderTimeInForce.GFN,
|
||||
].includes(orderTimeInForce)
|
||||
) {
|
||||
return t(
|
||||
'Only GTT, GTC and GFA are permitted when market is in auction'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldErrors?.size?.type === 'required') {
|
||||
return t('An amount needs to be provided');
|
||||
}
|
||||
|
||||
if (fieldErrors?.size?.type === 'min') {
|
||||
return t(`The amount cannot be lower than "${step}"`);
|
||||
}
|
||||
|
||||
if (fieldErrors?.price?.type === 'required') {
|
||||
return t('A price needs to be provided');
|
||||
}
|
||||
|
||||
if (fieldErrors?.price?.type === 'min') {
|
||||
return t(`The price cannot be negative`);
|
||||
}
|
||||
|
||||
if (
|
||||
fieldErrors?.size?.type === 'validate' &&
|
||||
fieldErrors?.size?.message === ERROR_SIZE_DECIMAL
|
||||
) {
|
||||
if (market.positionDecimalPlaces === 0) {
|
||||
return t('No decimal amounts allowed for this order');
|
||||
}
|
||||
return t(
|
||||
`The amount field only takes up to ${market.positionDecimalPlaces} decimals`
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}, [
|
||||
keypair,
|
||||
step,
|
||||
market,
|
||||
fieldErrors?.size?.type,
|
||||
fieldErrors?.size?.message,
|
||||
fieldErrors?.price?.type,
|
||||
orderType,
|
||||
orderTimeInForce,
|
||||
]);
|
||||
|
||||
return invalidText;
|
||||
};
|
@ -1,15 +1,16 @@
|
||||
export * from './expiry-selector';
|
||||
export * from './type-selector';
|
||||
export * from './time-in-force-selector';
|
||||
export * from './submit-button';
|
||||
export * from './side-selector';
|
||||
export * from './deal-ticket';
|
||||
export * from './deal-ticket-limit-form';
|
||||
export * from './deal-ticket-market-form';
|
||||
export * from './deal-ticket-manager';
|
||||
export * from './order-dialog';
|
||||
export * from './use-order-state';
|
||||
export * from './use-order-submit';
|
||||
export * from './deal-ticket-container';
|
||||
export * from './components/expiry-selector';
|
||||
export * from './components/type-selector';
|
||||
export * from './components/time-in-force-selector';
|
||||
export * from './components/side-selector';
|
||||
export * from './components/deal-ticket';
|
||||
export * from './components/deal-ticket-amount';
|
||||
export * from './components/deal-ticket-limit-amount';
|
||||
export * from './components/deal-ticket-market-amount';
|
||||
export * from './components/deal-ticket-manager';
|
||||
export * from './components/order-dialog';
|
||||
export * from './components/deal-ticket-container';
|
||||
export * from './__generated__/DealTicketQuery';
|
||||
export * from './__generated__/OrderEvent';
|
||||
export * from './utils/get-default-order';
|
||||
export * from './hooks/use-order-submit';
|
||||
export * from './hooks/use-order-validation';
|
||||
|
@ -1,89 +0,0 @@
|
||||
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import { useMemo } from 'react';
|
||||
import type { Order } from './use-order-state';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { TransactionStatus } from './deal-ticket';
|
||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface SubmitButtonProps {
|
||||
transactionStatus: TransactionStatus;
|
||||
market: DealTicketQuery_market;
|
||||
order: Order;
|
||||
}
|
||||
|
||||
export const SubmitButton = ({
|
||||
market,
|
||||
transactionStatus,
|
||||
order,
|
||||
}: SubmitButtonProps) => {
|
||||
const { keypair } = useVegaWallet();
|
||||
|
||||
const invalidText = useMemo(() => {
|
||||
if (!keypair) {
|
||||
return t('No public key selected');
|
||||
}
|
||||
|
||||
if (keypair.tainted) {
|
||||
return t('Selected public key has been tainted');
|
||||
}
|
||||
|
||||
if (market.state !== MarketState.Active) {
|
||||
if (market.state === MarketState.Suspended) {
|
||||
return t('Market is currently suspended');
|
||||
}
|
||||
|
||||
if (
|
||||
market.state === MarketState.Proposed ||
|
||||
market.state === MarketState.Pending
|
||||
) {
|
||||
return t('Market is not active yet');
|
||||
}
|
||||
|
||||
return t('Market is no longer active');
|
||||
}
|
||||
|
||||
if (market.tradingMode !== MarketTradingMode.Continuous) {
|
||||
if (order.type === OrderType.Market) {
|
||||
return t('Only limit orders are permitted when market is in auction');
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
OrderTimeInForce.FOK,
|
||||
OrderTimeInForce.IOC,
|
||||
OrderTimeInForce.GFN,
|
||||
].includes(order.timeInForce)
|
||||
) {
|
||||
return t(
|
||||
'Only GTT, GTC and GFA are permitted when market is in auction'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}, [keypair, market, order]);
|
||||
|
||||
const disabled = transactionStatus === 'pending' || Boolean(invalidText);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
className="w-full mb-8"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={disabled}
|
||||
data-testid="place-order"
|
||||
>
|
||||
{transactionStatus === 'pending' ? t('Pending...') : t('Place order')}
|
||||
</Button>
|
||||
{invalidText && (
|
||||
<InputError className="mb-8" data-testid="dealticket-error-message">
|
||||
{invalidText}
|
||||
</InputError>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,79 +0,0 @@
|
||||
import type { OrderSide } from '@vegaprotocol/wallet';
|
||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
export interface Order {
|
||||
size: string;
|
||||
type: OrderType;
|
||||
timeInForce: OrderTimeInForce;
|
||||
side: OrderSide | null;
|
||||
price?: string;
|
||||
expiration?: Date;
|
||||
}
|
||||
|
||||
export type UpdateOrder = (order: Partial<Order>) => void;
|
||||
|
||||
export const useOrderState = (defaultOrder: Order): [Order, UpdateOrder] => {
|
||||
const [order, setOrder] = useState<Order>(defaultOrder);
|
||||
|
||||
const updateOrder = useCallback((orderUpdate: Partial<Order>) => {
|
||||
setOrder((curr) => {
|
||||
// Type is switching to market so return new market order object with correct defaults
|
||||
if (
|
||||
orderUpdate.type === OrderType.Market &&
|
||||
curr.type !== OrderType.Market
|
||||
) {
|
||||
// Check if provided TIF or current TIF is valid for a market order and default
|
||||
// to IOC if its not
|
||||
|
||||
const isTifValid = (tif: OrderTimeInForce) => {
|
||||
return tif === OrderTimeInForce.FOK || tif === OrderTimeInForce.IOC;
|
||||
};
|
||||
|
||||
// Default
|
||||
let timeInForce = OrderTimeInForce.IOC;
|
||||
|
||||
if (orderUpdate.timeInForce) {
|
||||
if (isTifValid(orderUpdate.timeInForce)) {
|
||||
timeInForce = orderUpdate.timeInForce;
|
||||
}
|
||||
} else {
|
||||
if (isTifValid(curr.timeInForce)) {
|
||||
timeInForce = curr.timeInForce;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: orderUpdate.type,
|
||||
size: orderUpdate.size || curr.size,
|
||||
side: orderUpdate.side || curr.side,
|
||||
timeInForce,
|
||||
price: undefined,
|
||||
expiration: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// Type is switching to limit so return new order object with correct defaults
|
||||
if (
|
||||
orderUpdate.type === OrderType.Limit &&
|
||||
curr.type !== OrderType.Limit
|
||||
) {
|
||||
return {
|
||||
type: orderUpdate.type,
|
||||
size: orderUpdate.size || curr.size,
|
||||
side: orderUpdate.side || curr.side,
|
||||
timeInForce: orderUpdate.timeInForce || curr.timeInForce,
|
||||
price: orderUpdate.price || '0',
|
||||
expiration: orderUpdate.expiration || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...curr,
|
||||
...orderUpdate,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
return [order, updateOrder];
|
||||
};
|
28
libs/deal-ticket/src/utils/get-default-order.ts
Normal file
28
libs/deal-ticket/src/utils/get-default-order.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { OrderTimeInForce, OrderType, OrderSide } from '@vegaprotocol/wallet';
|
||||
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||
import { toDecimal } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export type Order =
|
||||
| {
|
||||
size: string;
|
||||
type: OrderType.Market;
|
||||
timeInForce: OrderTimeInForce;
|
||||
side: OrderSide;
|
||||
price?: never;
|
||||
expiration?: never;
|
||||
}
|
||||
| {
|
||||
size: string;
|
||||
type: OrderType.Limit;
|
||||
timeInForce: OrderTimeInForce;
|
||||
side: OrderSide;
|
||||
price?: string;
|
||||
expiration?: Date;
|
||||
};
|
||||
|
||||
export const getDefaultOrder = (market: DealTicketQuery_market): Order => ({
|
||||
type: OrderType.Market,
|
||||
side: OrderSide.Buy,
|
||||
timeInForce: OrderTimeInForce.IOC,
|
||||
size: String(toDecimal(market.positionDecimalPlaces)),
|
||||
});
|
12
libs/deal-ticket/src/utils/validate-size.ts
Normal file
12
libs/deal-ticket/src/utils/validate-size.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export const ERROR_SIZE_DECIMAL = 'step';
|
||||
|
||||
export const validateSize = (step: number) => {
|
||||
const [, stepDecimals = ''] = String(step).split('.');
|
||||
return (value: string) => {
|
||||
const [, valueDecimals = ''] = value.split('.');
|
||||
if (stepDecimals.length < valueDecimals.length) {
|
||||
return ERROR_SIZE_DECIMAL;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
};
|
@ -8,7 +8,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
|
@ -1 +0,0 @@
|
||||
export const FAUCETABLE = process.env['NX_VEGA_ENV'] !== 'MAINNET';
|
@ -41,6 +41,7 @@ beforeEach(() => {
|
||||
max: new BigNumber(20),
|
||||
},
|
||||
allowance: new BigNumber(30),
|
||||
isFaucetable: true,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -24,7 +24,6 @@ import { useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { DepositLimits } from './deposit-limits';
|
||||
import { FAUCETABLE } from '../config';
|
||||
import type { Asset } from './deposit-manager';
|
||||
|
||||
interface FormFields {
|
||||
@ -51,6 +50,7 @@ export interface DepositFormProps {
|
||||
max: BigNumber;
|
||||
} | null;
|
||||
allowance: BigNumber | undefined;
|
||||
isFaucetable?: boolean;
|
||||
}
|
||||
|
||||
export const DepositForm = ({
|
||||
@ -63,6 +63,7 @@ export const DepositForm = ({
|
||||
requestFaucet,
|
||||
limits,
|
||||
allowance,
|
||||
isFaucetable,
|
||||
}: DepositFormProps) => {
|
||||
const { account } = useWeb3React();
|
||||
const { keypair } = useVegaWallet();
|
||||
@ -166,7 +167,7 @@ export const DepositForm = ({
|
||||
{errors.asset.message}
|
||||
</InputError>
|
||||
)}
|
||||
{FAUCETABLE && selectedAsset && (
|
||||
{isFaucetable && selectedAsset && (
|
||||
<UseButton onClick={requestFaucet}>
|
||||
{t(`Get ${selectedAsset.symbol}`)}
|
||||
</UseButton>
|
||||
|
@ -34,6 +34,7 @@ interface DepositManagerProps {
|
||||
bridgeAddress: string;
|
||||
assets: Asset[];
|
||||
initialAssetId?: string;
|
||||
isFaucetable?: boolean;
|
||||
}
|
||||
|
||||
export const DepositManager = ({
|
||||
@ -41,6 +42,7 @@ export const DepositManager = ({
|
||||
bridgeAddress,
|
||||
assets,
|
||||
initialAssetId,
|
||||
isFaucetable,
|
||||
}: DepositManagerProps) => {
|
||||
const [assetId, setAssetId] = useState<string | undefined>(initialAssetId);
|
||||
|
||||
@ -54,7 +56,7 @@ export const DepositManager = ({
|
||||
asset?.source.__typename === 'ERC20'
|
||||
? asset.source.contractAddress
|
||||
: undefined,
|
||||
process.env['NX_VEGA_ENV'] !== 'MAINNET'
|
||||
isFaucetable
|
||||
);
|
||||
const bridgeContract = useBridgeContract();
|
||||
|
||||
@ -101,6 +103,7 @@ export const DepositManager = ({
|
||||
requestFaucet={faucet.perform}
|
||||
limits={limits}
|
||||
allowance={allowance}
|
||||
isFaucetable={isFaucetable}
|
||||
/>
|
||||
<TransactionDialog {...approve.transaction} name="approve" />
|
||||
<TransactionDialog {...faucet.transaction} name="faucet" />
|
||||
|
@ -8,7 +8,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
|
@ -8,7 +8,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
|
@ -2,14 +2,17 @@ import { useQuery } from '@apollo/client';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import { AsyncRenderer, Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { useState } from 'react';
|
||||
import { MARKET_LIST_QUERY } from '../markets-container/markets-data-provider';
|
||||
import type { MarketList } from '../markets-container/__generated__/MarketList';
|
||||
|
||||
import { SelectMarketList } from './select-market-list';
|
||||
|
||||
export const LandingDialog = () => {
|
||||
const [open, setOpen] = useState(true);
|
||||
interface LandingDialogProps {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const LandingDialog = ({ open, setOpen }: LandingDialogProps) => {
|
||||
const setClose = () => setOpen(false);
|
||||
|
||||
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
|
||||
@ -21,17 +24,15 @@ export const LandingDialog = () => {
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
{
|
||||
<Dialog
|
||||
title={t('Select a market to get started')}
|
||||
intent={Intent.Prompt}
|
||||
open={open}
|
||||
onChange={setClose}
|
||||
titleClassNames="font-bold font-sans text-3xl tracking-tight mb-0 pl-8"
|
||||
>
|
||||
<SelectMarketList data={data} />
|
||||
</Dialog>
|
||||
}
|
||||
<Dialog
|
||||
title={t('Select a market to get started')}
|
||||
intent={Intent.Prompt}
|
||||
open={open}
|
||||
onChange={setClose}
|
||||
titleClassNames="font-bold font-sans text-3xl tracking-tight mb-0 pl-8"
|
||||
>
|
||||
<SelectMarketList data={data} onSelect={setClose} />
|
||||
</Dialog>
|
||||
</AsyncRenderer>
|
||||
);
|
||||
};
|
||||
|
@ -1,15 +1,41 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import type { MarketList } from '../__generated__/MarketList';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { MarketList } from '../markets-container/__generated__/MarketList';
|
||||
import { SelectMarketList } from './select-market-list';
|
||||
|
||||
jest.mock(
|
||||
'next/link',
|
||||
() =>
|
||||
({ children }: { children: ReactNode }) =>
|
||||
children
|
||||
);
|
||||
|
||||
describe('SelectMarketList', () => {
|
||||
it('should render', () => {
|
||||
render(<SelectMarketList data={mockData.data as MarketList} />);
|
||||
render(
|
||||
<SelectMarketList
|
||||
data={mockData.data as MarketList}
|
||||
onSelect={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText('AAPL.MF21')).toBeTruthy();
|
||||
expect(screen.getByText('-3.14%')).toBeTruthy();
|
||||
expect(screen.getByText('141.75')).toBeTruthy();
|
||||
expect(screen.getByText('Or view full market list')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call onSelect callback', () => {
|
||||
const onSelect = jest.fn();
|
||||
const expectedMarket = mockData.data.markets[0];
|
||||
render(
|
||||
<SelectMarketList
|
||||
data={mockData.data as MarketList}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(screen.getByTestId(`market-link-${expectedMarket.id}`));
|
||||
expect(onSelect).toHaveBeenCalledWith(expectedMarket.id);
|
||||
});
|
||||
});
|
||||
|
||||
const mockData = {
|
||||
|
@ -10,11 +10,12 @@ import type { MarketList } from '../markets-container/__generated__/MarketList';
|
||||
|
||||
export interface SelectMarketListProps {
|
||||
data: MarketList | undefined;
|
||||
onSelect: (id: string) => void;
|
||||
}
|
||||
|
||||
type CandleClose = Required<string>;
|
||||
|
||||
export const SelectMarketList = ({ data }: SelectMarketListProps) => {
|
||||
export const SelectMarketList = ({ data, onSelect }: SelectMarketListProps) => {
|
||||
const thClassNames = (direction: 'left' | 'right') =>
|
||||
`px-8 text-${direction} font-sans font-normal text-ui-small leading-9 mb-0 text-dark/80 dark:text-white/80`;
|
||||
const tdClassNames =
|
||||
@ -49,8 +50,15 @@ export const SelectMarketList = ({ data }: SelectMarketListProps) => {
|
||||
<td className={`${boldUnderlineClassNames} relative`}>
|
||||
<Link
|
||||
href={`/markets/${id}?portfolio=orders&trade=orderbook&chart=candles`}
|
||||
passHref={true}
|
||||
>
|
||||
{marketName}
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a
|
||||
onClick={() => onSelect(id)}
|
||||
data-testid={`market-link-${id}`}
|
||||
>
|
||||
{marketName}
|
||||
</a>
|
||||
</Link>
|
||||
</td>
|
||||
<td className={tdClassNames}>
|
||||
|
@ -3,7 +3,7 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { Interval, MarketState, MarketTradingMode } from "@vegaprotocol/types";
|
||||
import { Interval } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: MarketList
|
||||
@ -15,14 +15,6 @@ export interface MarketList_markets_data_market {
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
state: MarketState;
|
||||
/**
|
||||
* Current mode of execution of the market
|
||||
*/
|
||||
tradingMode: MarketTradingMode;
|
||||
}
|
||||
|
||||
export interface MarketList_markets_data {
|
||||
@ -31,14 +23,6 @@ export interface MarketList_markets_data {
|
||||
* market id of the associated mark price
|
||||
*/
|
||||
market: MarketList_markets_data_market;
|
||||
/**
|
||||
* the highest price level on an order book for buy orders.
|
||||
*/
|
||||
bestBidPrice: string;
|
||||
/**
|
||||
* the lowest price level on an order book for offer orders.
|
||||
*/
|
||||
bestOfferPrice: string;
|
||||
/**
|
||||
* the mark price (actually an unsigned int)
|
||||
*/
|
||||
@ -66,7 +50,7 @@ export interface MarketList_markets_tradableInstrument_instrument {
|
||||
/**
|
||||
* Metadata for this instrument
|
||||
*/
|
||||
metadata?: MarketList_markets_tradableInstrument_instrument_metadata;
|
||||
metadata: MarketList_markets_tradableInstrument_instrument_metadata;
|
||||
}
|
||||
|
||||
export interface MarketList_markets_tradableInstrument {
|
||||
@ -110,14 +94,14 @@ export interface MarketList_markets {
|
||||
/**
|
||||
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
|
||||
* number denominated in the currency of the Market. (uint64)
|
||||
*
|
||||
*
|
||||
* Examples:
|
||||
* Currency Balance decimalPlaces Real Balance
|
||||
* GBP 100 0 GBP 100
|
||||
* GBP 100 2 GBP 1.00
|
||||
* GBP 100 4 GBP 0.01
|
||||
* GBP 1 4 GBP 0.0001 ( 0.01p )
|
||||
*
|
||||
*
|
||||
* GBX (pence) 100 0 GBP 1.00 (100p )
|
||||
* GBX (pence) 100 2 GBP 0.01 ( 1p )
|
||||
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
|
||||
|
@ -2,3 +2,4 @@ export * from './market-list-table';
|
||||
export * from './markets-container';
|
||||
export * from './markets-data-provider';
|
||||
export * from './summary-cell';
|
||||
export * from './__generated__/MarketList';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user