Merge branch 'master' of github.com:vegaprotocol/frontend-monorepo

This commit is contained in:
madalinaraicu 2022-06-02 10:19:29 +01:00
commit c6284a91ef
137 changed files with 2070 additions and 1208 deletions

View File

@ -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

View File

@ -10,7 +10,7 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},

View File

@ -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>
);
}

View File

@ -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": [],

View File

@ -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>
);
}

View File

@ -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>
);

View File

@ -8,7 +8,7 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},

View File

@ -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",

View File

@ -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",

View File

@ -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>
);
}

View File

@ -8,7 +8,7 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},

View File

@ -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>
);

View File

@ -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 (

View File

@ -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(() => {

View File

@ -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>
);

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>
);
})}

View File

@ -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 };
};

View File

@ -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;

View File

@ -1,5 +1,4 @@
export * from './flags';
export * from './ethereum';
export * from './links';
export * from './network-params';
export * from './vega';

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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,
]);
};

View File

@ -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);

View File

@ -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>
);
};

View File

@ -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>

View File

@ -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()}

View File

@ -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 },

View File

@ -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(

View File

@ -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;

View File

@ -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')}&nbsp;
<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>
</>
);
};

View File

@ -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>

View File

@ -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,
};
});

View File

@ -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 &&

View File

@ -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>

View File

@ -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();

View File

@ -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>
) : (
'-'
)}

View File

@ -10,8 +10,7 @@
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],

View File

@ -10,6 +10,7 @@ export const generateDealTicketQuery = (
market: {
id: 'market-id',
decimalPlaces: 2,
positionDecimalPlaces: 1,
state: MarketState.Active,
tradingMode: MarketTradingMode.Continuous,
tradableInstrument: {

View 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);
};

View File

@ -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'
);
}

View File

@ -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();
});

View File

@ -11,7 +11,7 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},

View File

@ -0,0 +1 @@
export * from './vega-wallet-container';

View File

@ -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();
});
});

View File

@ -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}</>;
};

View File

@ -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]);

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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)}
/>
</>
);
}}
/>

View File

@ -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'}
/>
);
}}

View File

@ -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>
);
};

View File

@ -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}

View File

@ -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>
);
};

View 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 });
},
}));

View File

@ -0,0 +1 @@
export * from './global';

View File

@ -9,7 +9,6 @@
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true
},

View File

@ -8,7 +8,7 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},

View File

@ -8,7 +8,7 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},

View File

@ -17,7 +17,7 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}

View File

@ -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
*/

View 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} />;
};

View File

@ -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 {

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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'), {

View 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>
);
};

View File

@ -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 (

View File

@ -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';

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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}
/>
</>
);
};

View File

@ -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}
/>
</>
);
};

View File

@ -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>
);
};

View File

@ -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
},
});
});

View File

@ -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,

View 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);
});

View 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;
};

View File

@ -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';

View File

@ -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>
)}
</>
);
};

View File

@ -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];
};

View 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)),
});

View 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;
};
};

View File

@ -8,7 +8,7 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},

View File

@ -1 +0,0 @@
export const FAUCETABLE = process.env['NX_VEGA_ENV'] !== 'MAINNET';

View File

@ -41,6 +41,7 @@ beforeEach(() => {
max: new BigNumber(20),
},
allowance: new BigNumber(30),
isFaucetable: true,
};
});

View File

@ -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>

View File

@ -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" />

View File

@ -8,7 +8,7 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},

View File

@ -8,7 +8,7 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},

View File

@ -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>
);
};

View File

@ -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 = {

View File

@ -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}>

View File

@ -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 )

View File

@ -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