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). 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 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 - For either command you may use `--all` to run across the entire repository
### Further help with Nx ### Further help with Nx

View File

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

View File

@ -2,7 +2,10 @@ import { useState, useEffect, useMemo } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { ApolloProvider } from '@apollo/client'; import { ApolloProvider } from '@apollo/client';
import { ThemeContext } from '@vegaprotocol/react-helpers'; 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 { createClient } from './lib/apollo-client';
import { Nav } from './components/nav'; import { Nav } from './components/nav';
import { Header } from './components/header'; import { Header } from './components/header';
@ -23,6 +26,7 @@ function App() {
const client = useMemo(() => createClient(DATA_SOURCES.dataNodeUrl), []); const client = useMemo(() => createClient(DATA_SOURCES.dataNodeUrl), []);
return ( return (
<EnvironmentProvider>
<ThemeContext.Provider value={theme}> <ThemeContext.Provider value={theme}>
<TendermintWebsocketProvider> <TendermintWebsocketProvider>
<ApolloProvider client={client}> <ApolloProvider client={client}>
@ -44,6 +48,7 @@ function App() {
</ApolloProvider> </ApolloProvider>
</TendermintWebsocketProvider> </TendermintWebsocketProvider>
</ThemeContext.Provider> </ThemeContext.Provider>
</EnvironmentProvider>
); );
} }

View File

@ -11,8 +11,7 @@
"noPropertyAccessFromIndexSignature": false, "noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"lib": ["es5", "es6", "dom", "dom.iterable"], "lib": ["es5", "es6", "dom", "dom.iterable"]
"resolveJsonModule": true
}, },
"files": [], "files": [],
"include": [], "include": [],

View File

@ -9,6 +9,7 @@ import {
VegaManageDialog, VegaManageDialog,
VegaWalletProvider, VegaWalletProvider,
} from '@vegaprotocol/wallet'; } from '@vegaprotocol/wallet';
import { EnvironmentProvider } from '@vegaprotocol/react-helpers';
import { VegaWalletConnectButton } from './components/vega-wallet-connect-button'; import { VegaWalletConnectButton } from './components/vega-wallet-connect-button';
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit'; import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
import { Connectors } from './lib/vega-connectors'; import { Connectors } from './lib/vega-connectors';
@ -37,6 +38,7 @@ function App() {
}, [location]); }, [location]);
return ( return (
<EnvironmentProvider>
<ThemeContext.Provider value={theme}> <ThemeContext.Provider value={theme}>
<ApolloProvider client={client}> <ApolloProvider client={client}>
<VegaWalletProvider> <VegaWalletProvider>
@ -82,6 +84,7 @@ function App() {
</VegaWalletProvider> </VegaWalletProvider>
</ApolloProvider> </ApolloProvider>
</ThemeContext.Provider> </ThemeContext.Provider>
</EnvironmentProvider>
); );
} }

View File

@ -1,40 +1,54 @@
import * as React from 'react'; import * as React from 'react';
import type { FormEvent } from 'react'; import { useForm, Controller } from 'react-hook-form';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import { Stepper } from '../stepper'; 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 { import {
ExpirySelector, ExpirySelector,
SideSelector, SideSelector,
SubmitButton,
TimeInForceSelector, TimeInForceSelector,
TypeSelector, TypeSelector,
useOrderState, getDefaultOrder,
useOrderValidation,
useOrderSubmit, useOrderSubmit,
DealTicketLimitForm, DealTicketAmount,
DealTicketMarketForm,
} from '@vegaprotocol/deal-ticket'; } from '@vegaprotocol/deal-ticket';
import { import {
OrderSide,
OrderTimeInForce, OrderTimeInForce,
OrderType, OrderType,
VegaTxStatus, VegaTxStatus,
} from '@vegaprotocol/wallet'; } from '@vegaprotocol/wallet';
import { addDecimal } from '@vegaprotocol/react-helpers'; import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
interface DealTicketMarketProps { interface DealTicketMarketProps {
market: DealTicketQuery_market; market: DealTicketQuery_market;
} }
const DEFAULT_ORDER: Order = {
type: OrderType.Market,
side: OrderSide.Buy,
size: '1',
timeInForce: OrderTimeInForce.IOC,
};
export const DealTicketSteps = ({ market }: DealTicketMarketProps) => { 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 { submit, transaction } = useOrderSubmit(market);
const transactionStatus = const transactionStatus =
@ -43,39 +57,14 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
? 'pending' ? 'pending'
: 'default'; : 'default';
let ticket = null; const onSubmit = React.useCallback(
(order: Order) => {
if (order.type === OrderType.Market) { if (transactionStatus !== 'pending') {
ticket = ( submit(order);
<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} },
/> [transactionStatus, submit]
); );
} 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 steps = [ const steps = [
{ {
@ -87,9 +76,12 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
label: 'Select Order Type', label: 'Select Order Type',
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`, description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
component: ( component: (
<TypeSelector <Controller
order={order} name="type"
onSelect={(type) => updateOrder({ 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', label: 'Select Market Position',
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`, description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
component: ( component: (
<SideSelector <Controller
order={order} name="side"
onSelect={(side) => updateOrder({ side })} control={control}
render={({ field }) => (
<SideSelector value={field.value} onSelect={field.onChange} />
)}
/> />
), ),
}, },
@ -107,25 +102,47 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
label: 'Select Order Size', label: 'Select Order Size',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', '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', 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.`, description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
component: ( component: (
<> <>
<Controller
name="timeInForce"
control={control}
render={({ field }) => (
<TimeInForceSelector <TimeInForceSelector
order={order} value={field.value}
onSelect={(timeInForce) => updateOrder({ timeInForce })} orderType={orderType}
onSelect={field.onChange}
/> />
{order.timeInForce === OrderTimeInForce.GTT && ( )}
/>
{orderType === OrderType.Limit &&
orderTimeInForce === OrderTimeInForce.GTT && (
<Controller
name="expiration"
control={control}
render={({ field }) => (
<ExpirySelector <ExpirySelector
order={order} value={field.value}
onSelect={(date) => { onSelect={field.onChange}
if (date) { />
updateOrder({ expiration: date }); )}
}
}}
/> />
)} )}
</> </>
@ -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.`, description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
component: ( component: (
<Box sx={{ mb: 2 }}> <Box sx={{ mb: 2 }}>
<SubmitButton {invalidText && (
transactionStatus={transactionStatus} <InputError className="mb-8" data-testid="dealticket-error-message">
market={market} {invalidText}
order={order} </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> </Box>
), ),
disabled: true, disabled: true,
@ -148,7 +176,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
]; ];
return ( return (
<form onSubmit={handleSubmit} className="px-4 py-8"> <form onSubmit={handleSubmit(onSubmit)} className="px-4 py-8">
<Stepper steps={steps} /> <Stepper steps={steps} />
</form> </form>
); );

View File

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

View File

@ -488,7 +488,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "97499.58", "total_added": "97499.58",
"total_removed": "0", "total_removed": "0",
"locked_amount": "71824.744464790495556796", "locked_amount": "71359.360754532949904994",
"deposits": [ "deposits": [
{ {
"amount": "97499.58", "amount": "97499.58",
@ -521,7 +521,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "135173.4239508", "total_added": "135173.4239508",
"total_removed": "0", "total_removed": "0",
"locked_amount": "98172.02565853694305378077588", "locked_amount": "97535.92647176062142134244004",
"deposits": [ "deposits": [
{ {
"amount": "135173.4239508", "amount": "135173.4239508",
@ -554,7 +554,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "32499.86", "total_added": "32499.86",
"total_removed": "0", "total_removed": "0",
"locked_amount": "30215.429649344600035924", "locked_amount": "30019.65075918605161088",
"deposits": [ "deposits": [
{ {
"amount": "32499.86", "amount": "32499.86",
@ -587,7 +587,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "10833.29", "total_added": "10833.29",
"total_removed": "0", "total_removed": "0",
"locked_amount": "9834.8291472726030138", "locked_amount": "9771.105018319376583514",
"deposits": [ "deposits": [
{ {
"amount": "10833.29", "amount": "10833.29",
@ -675,7 +675,7 @@
"tranche_end": "2022-11-01T00:00:00.000Z", "tranche_end": "2022-11-01T00:00:00.000Z",
"total_added": "22500", "total_added": "22500",
"total_removed": "0", "total_removed": "0",
"locked_amount": "18790.38156702898575", "locked_amount": "18545.82059556159525",
"deposits": [ "deposits": [
{ {
"amount": "15000", "amount": "15000",
@ -761,7 +761,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z", "tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38", "total_added": "1939928.38",
"total_removed": "0", "total_removed": "0",
"locked_amount": "1939928.38", "locked_amount": "1938140.454506446441067284",
"deposits": [ "deposits": [
{ {
"amount": "1852091.69", "amount": "1852091.69",
@ -814,7 +814,7 @@
"tranche_end": "2022-06-02T00:00:00.000Z", "tranche_end": "2022-06-02T00:00:00.000Z",
"total_added": "1121510.3963", "total_added": "1121510.3963",
"total_removed": "922703.8062907670614", "total_removed": "922703.8062907670614",
"locked_amount": "10251.1276633719903559980853", "locked_amount": "0",
"deposits": [ "deposits": [
{ {
"amount": "7187.236", "amount": "7187.236",
@ -1777,7 +1777,7 @@
"tranche_end": "2022-09-30T00:00:00.000Z", "tranche_end": "2022-09-30T00:00:00.000Z",
"total_added": "60916.66666633337", "total_added": "60916.66666633337",
"total_removed": "17568.575895506846757997", "total_removed": "17568.575895506846757997",
"locked_amount": "19003.432880158768918954969915924", "locked_amount": "18691.0451412597139137896684208311",
"deposits": [ "deposits": [
{ {
"amount": "2833.333333", "amount": "2833.333333",
@ -3206,8 +3206,8 @@
"tranche_id": 10, "tranche_id": 10,
"tranche_start": "2021-07-15T23:37:11.000Z", "tranche_start": "2021-07-15T23:37:11.000Z",
"tranche_end": "2021-07-15T23:37:11.000Z", "tranche_end": "2021-07-15T23:37:11.000Z",
"total_added": "3425768.150000000000000001", "total_added": "3475768.150000000000000001",
"total_removed": "3417578.640000000000000001", "total_removed": "3467578.640000000000000001",
"locked_amount": "0", "locked_amount": "0",
"deposits": [ "deposits": [
{ {
@ -3230,6 +3230,11 @@
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"tx": "0x353c9a2464262be10f3a07acaf2be06dd90749c60da1af070ff053d2dcc2f8a2" "tx": "0x353c9a2464262be10f3a07acaf2be06dd90749c60da1af070ff053d2dcc2f8a2"
}, },
{
"amount": "50000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"tx": "0xf96efc6a81c0e18f7d760f060797dbb213b4b472855fc8abd578e0be183d1d6f"
},
{ {
"amount": "1", "amount": "1",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
@ -3617,6 +3622,11 @@
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"tx": "0x499106f146fbaa9dd63bfbd3e6452dcdd5d845371c81e64962587c0b8bb43d10" "tx": "0x499106f146fbaa9dd63bfbd3e6452dcdd5d845371c81e64962587c0b8bb43d10"
}, },
{
"amount": "50000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"tx": "0x3a7c5f9a15ec899a68ad41701bfafd6f3c8ce55954dbeaeb343680378e6c00c0"
},
{ {
"amount": "1", "amount": "1",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
@ -3961,6 +3971,12 @@
"tranche_id": 10, "tranche_id": 10,
"tx": "0x353c9a2464262be10f3a07acaf2be06dd90749c60da1af070ff053d2dcc2f8a2" "tx": "0x353c9a2464262be10f3a07acaf2be06dd90749c60da1af070ff053d2dcc2f8a2"
}, },
{
"amount": "50000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"tranche_id": 10,
"tx": "0xf96efc6a81c0e18f7d760f060797dbb213b4b472855fc8abd578e0be183d1d6f"
},
{ {
"amount": "1", "amount": "1",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
@ -3999,6 +4015,12 @@
"tranche_id": 10, "tranche_id": 10,
"tx": "0x499106f146fbaa9dd63bfbd3e6452dcdd5d845371c81e64962587c0b8bb43d10" "tx": "0x499106f146fbaa9dd63bfbd3e6452dcdd5d845371c81e64962587c0b8bb43d10"
}, },
{
"amount": "50000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"tranche_id": 10,
"tx": "0x3a7c5f9a15ec899a68ad41701bfafd6f3c8ce55954dbeaeb343680378e6c00c0"
},
{ {
"amount": "1", "amount": "1",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f", "user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
@ -4018,8 +4040,8 @@
"tx": "0xac16a4ce688d40a482a59914d68c3a676592f8804ee8f0781b66a4ba5ccfbdfc" "tx": "0xac16a4ce688d40a482a59914d68c3a676592f8804ee8f0781b66a4ba5ccfbdfc"
} }
], ],
"total_tokens": "279451", "total_tokens": "329451",
"withdrawn_tokens": "279451", "withdrawn_tokens": "329451",
"remaining_tokens": "0" "remaining_tokens": "0"
}, },
{ {
@ -5017,9 +5039,9 @@
"tranche_id": 11, "tranche_id": 11,
"tranche_start": "2021-09-03T00:00:00.000Z", "tranche_start": "2021-09-03T00:00:00.000Z",
"tranche_end": "2022-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", "total_removed": "2897.73152892253",
"locked_amount": "3753.6048590816850461007780566971080671", "locked_amount": "3826.6258515664639083007616186263318113",
"deposits": [ "deposits": [
{ {
"amount": "10", "amount": "10",
@ -5606,6 +5628,51 @@
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c", "user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
"tx": "0xb5ce5dbf53dc455d902f8be045d73604ac520aad1819209281568c36f2a67fb8" "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", "amount": "25",
"user": "0xc6A53Dbb3423555C990Fc842A28CF58edC35DC73", "user": "0xc6A53Dbb3423555C990Fc842A28CF58edC35DC73",
@ -8929,12 +8996,66 @@
"user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c", "user": "0x4cf8879dC68ebe9F003E6231CB8B704FAbEA8d9c",
"tranche_id": 11, "tranche_id": 11,
"tx": "0xb5ce5dbf53dc455d902f8be045d73604ac520aad1819209281568c36f2a67fb8" "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": [], "withdrawals": [],
"total_tokens": "10", "total_tokens": "610",
"withdrawn_tokens": "0", "withdrawn_tokens": "0",
"remaining_tokens": "10" "remaining_tokens": "610"
}, },
{ {
"address": "0xc6A53Dbb3423555C990Fc842A28CF58edC35DC73", "address": "0xc6A53Dbb3423555C990Fc842A28CF58edC35DC73",
@ -12786,7 +12907,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671", "total_added": "3732368.4671",
"total_removed": "74162.9780761646031", "total_removed": "74162.9780761646031",
"locked_amount": "3019082.347375474584450246", "locked_amount": "3002748.41446940756547250008",
"deposits": [ "deposits": [
{ {
"amount": "1998.95815", "amount": "1998.95815",
@ -15450,8 +15571,8 @@
"tranche_start": "2021-11-05T00:00:00.000Z", "tranche_start": "2021-11-05T00:00:00.000Z",
"tranche_end": "2023-05-05T00:00:00.000Z", "tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999", "total_added": "14597706.0446472999",
"total_removed": "1562375.666880815858067983", "total_removed": "1563001.535509480008878233",
"locked_amount": "9054416.05714107182492323023185274", "locked_amount": "9000945.53475187470322777308704175",
"deposits": [ "deposits": [
{ {
"amount": "129284.449", "amount": "129284.449",
@ -15790,6 +15911,11 @@
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x47879bd74b0adbf7cbfd0ea1e0175bcc202ac5c224dacf94d7a4ef6a2f1de9a0" "tx": "0x47879bd74b0adbf7cbfd0ea1e0175bcc202ac5c224dacf94d7a4ef6a2f1de9a0"
}, },
{
"amount": "625.86862866415081025",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x3f1666dc9c401996a37b4543b7945d14f6d66561cd1d0a84caa30b92fc55408e"
},
{ {
"amount": "58127.81687116447134108", "amount": "58127.81687116447134108",
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8", "user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
@ -17195,6 +17321,12 @@
"tranche_id": 3, "tranche_id": 3,
"tx": "0x47879bd74b0adbf7cbfd0ea1e0175bcc202ac5c224dacf94d7a4ef6a2f1de9a0" "tx": "0x47879bd74b0adbf7cbfd0ea1e0175bcc202ac5c224dacf94d7a4ef6a2f1de9a0"
}, },
{
"amount": "625.86862866415081025",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0x3f1666dc9c401996a37b4543b7945d14f6d66561cd1d0a84caa30b92fc55408e"
},
{ {
"amount": "673.86655774325828525", "amount": "673.86655774325828525",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@ -18217,8 +18349,8 @@
} }
], ],
"total_tokens": "359123.469575", "total_tokens": "359123.469575",
"withdrawn_tokens": "136362.15277444105946275", "withdrawn_tokens": "136988.021403105210273",
"remaining_tokens": "222761.31680055894053725" "remaining_tokens": "222135.448171894789727"
}, },
{ {
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB", "address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
@ -19189,8 +19321,8 @@
"tranche_start": "2021-10-05T00:00:00.000Z", "tranche_start": "2021-10-05T00:00:00.000Z",
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303", "total_added": "5778205.3912159303",
"total_removed": "1270541.085579567787916742", "total_removed": "1292362.811321297942666742",
"locked_amount": "3260551.14128864886179085042406401", "locked_amount": "3239424.61450565009103150955191263",
"deposits": [ "deposits": [
{ {
"amount": "552496.6455", "amount": "552496.6455",
@ -19349,6 +19481,11 @@
"user": "0x6ae83EAB68b7112BaD5AfD72d6B24546AbFF137D", "user": "0x6ae83EAB68b7112BaD5AfD72d6B24546AbFF137D",
"tx": "0xb6a0d0df674d88b02c3cbfcd26df1c379545ea24ff8fa5a0f35810e7606a687d" "tx": "0xb6a0d0df674d88b02c3cbfcd26df1c379545ea24ff8fa5a0f35810e7606a687d"
}, },
{
"amount": "21821.72574173015475",
"user": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3",
"tx": "0x8e9f65bfea45a61e4f20108bc0ad1ce2bdeba8e355c7b1d4e024a3ffd73065a4"
},
{ {
"amount": "7013.412182841867109", "amount": "7013.412182841867109",
"user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75", "user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75",
@ -19844,6 +19981,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "21821.72574173015475",
"user": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3",
"tranche_id": 4,
"tx": "0x8e9f65bfea45a61e4f20108bc0ad1ce2bdeba8e355c7b1d4e024a3ffd73065a4"
},
{ {
"amount": "74521.210878260278004", "amount": "74521.210878260278004",
"user": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3", "user": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3",
@ -19852,8 +19995,8 @@
} }
], ],
"total_tokens": "220999.0582", "total_tokens": "220999.0582",
"withdrawn_tokens": "74521.210878260278004", "withdrawn_tokens": "96342.936619990432754",
"remaining_tokens": "146477.847321739721996" "remaining_tokens": "124656.121580009567246"
}, },
{ {
"address": "0x6ae83EAB68b7112BaD5AfD72d6B24546AbFF137D", "address": "0x6ae83EAB68b7112BaD5AfD72d6B24546AbFF137D",
@ -20221,10 +20364,15 @@
"tranche_id": 5, "tranche_id": 5,
"tranche_start": "2022-06-05T00:00:00.000Z", "tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "469355.6199999996", "total_added": "472355.6199999996",
"total_removed": "0", "total_removed": "0",
"locked_amount": "469355.6199999996", "locked_amount": "472355.6199999996",
"deposits": [ "deposits": [
{
"amount": "3000",
"user": "0xD18ffAa4a1d16f9eD9d3BE4078738Eeda3f160FD",
"tx": "0xca9ae1a4cdf8f152cc4f139b62ed65a266c39fc6d0180dc1237feee0d46eb0d3"
},
{ {
"amount": "22500", "amount": "22500",
"user": "0x3b208631B70e4a98bBee864effc7908501305c1f", "user": "0x3b208631B70e4a98bBee864effc7908501305c1f",
@ -26838,6 +26986,21 @@
], ],
"withdrawals": [], "withdrawals": [],
"users": [ "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", "address": "0x3b208631B70e4a98bBee864effc7908501305c1f",
"deposits": [ "deposits": [
@ -45856,8 +46019,8 @@
"tranche_start": "2021-12-05T00:00:00.000Z", "tranche_start": "2021-12-05T00:00:00.000Z",
"tranche_end": "2022-06-05T00:00:00.000Z", "tranche_end": "2022-06-05T00:00:00.000Z",
"total_added": "171288.42", "total_added": "171288.42",
"total_removed": "16804.8745535697803", "total_removed": "17067.9263679026803",
"locked_amount": "4389.09147635835649595952", "locked_amount": "2506.83382533958609789446",
"deposits": [ "deposits": [
{ {
"amount": "250", "amount": "250",
@ -50161,6 +50324,21 @@
"user": "0x4f829afFDe62De9a7E819c475D20556EdC85971f", "user": "0x4f829afFDe62De9a7E819c475D20556EdC85971f",
"tx": "0x5ec25c61dab464ed2d1caeb79d6172e60b0965bed21d9293c1c0424e83268118" "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", "amount": "252.08931115",
"user": "0xE245503f8a33eBDDD718915132869C9Ba9df37A8", "user": "0xE245503f8a33eBDDD718915132869C9Ba9df37A8",
@ -51319,10 +51497,17 @@
"tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4" "tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "39.1320029504",
"user": "0x2e745Bf52Bd236dA8C4944d82233da08BAE49DA1",
"tranche_id": 6,
"tx": "0x1a5ccef5c59ceb3f82e1d4121ef16b29590e7f07139f6a34690c3e3a85d139d0"
}
],
"total_tokens": "40", "total_tokens": "40",
"withdrawn_tokens": "0", "withdrawn_tokens": "39.1320029504",
"remaining_tokens": "40" "remaining_tokens": "0.8679970496"
}, },
{ {
"address": "0x505180B5570Ef6E607aA42DB4b67B5E7a8253C5c", "address": "0x505180B5570Ef6E607aA42DB4b67B5E7a8253C5c",
@ -58611,6 +58796,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "38.3342872425",
"user": "0x27049a430Df8b89Ae4f899b0383B0C876F9cAcEb",
"tranche_id": 6,
"tx": "0x2dadd46cff92cdcb974ba556776f69b136a526e05391e07618503526ef3667e0"
},
{ {
"amount": "41.3456291975", "amount": "41.3456291975",
"user": "0x27049a430Df8b89Ae4f899b0383B0C876F9cAcEb", "user": "0x27049a430Df8b89Ae4f899b0383B0C876F9cAcEb",
@ -58643,8 +58834,8 @@
} }
], ],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "207.9011656725", "withdrawn_tokens": "246.235452915",
"remaining_tokens": "42.0988343275" "remaining_tokens": "3.764547085"
}, },
{ {
"address": "0x42bC480928828C57c39649A7D10e41227b6d5E4F", "address": "0x42bC480928828C57c39649A7D10e41227b6d5E4F",
@ -63624,6 +63815,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "185.58552414",
"user": "0xcB7C51a63110F2669D33cadf593E838e7EdD8007",
"tranche_id": 6,
"tx": "0x0d774d5fcf278d1b33eb7942a6c350b2303693d90d67e733132de00d97589a78"
},
{ {
"amount": "58.3807266225", "amount": "58.3807266225",
"user": "0xcB7C51a63110F2669D33cadf593E838e7EdD8007", "user": "0xcB7C51a63110F2669D33cadf593E838e7EdD8007",
@ -63632,8 +63829,8 @@
} }
], ],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "58.3807266225", "withdrawn_tokens": "243.9662507625",
"remaining_tokens": "191.6192733775" "remaining_tokens": "6.0337492375"
}, },
{ {
"address": "0x4d53f5e09adeAD6288477a37eede4522aacBBfdF", "address": "0x4d53f5e09adeAD6288477a37eede4522aacBBfdF",

View File

@ -38,7 +38,7 @@
"tranche_end": "2022-11-26T13:48:10.000Z", "tranche_end": "2022-11-26T13:48:10.000Z",
"total_added": "100", "total_added": "100",
"total_removed": "0", "total_removed": "0",
"locked_amount": "49.106503678335867", "locked_amount": "48.558558472856417",
"deposits": [ "deposits": [
{ {
"amount": "100", "amount": "100",
@ -242,7 +242,7 @@
"tranche_end": "2022-10-12T00:53:20.000Z", "tranche_end": "2022-10-12T00:53:20.000Z",
"total_added": "100", "total_added": "100",
"total_removed": "0", "total_removed": "0",
"locked_amount": "36.63031773211568", "locked_amount": "36.08237252663623",
"deposits": [ "deposits": [
{ {
"amount": "100", "amount": "100",

View File

@ -3,6 +3,7 @@ import { DATA_SOURCES } from './config';
import { Header } from './components/header'; import { Header } from './components/header';
import { StatsManager } from '@vegaprotocol/network-stats'; import { StatsManager } from '@vegaprotocol/network-stats';
import { ThemeContext } from '@vegaprotocol/react-helpers'; import { ThemeContext } from '@vegaprotocol/react-helpers';
import { EnvironmentProvider } from '@vegaprotocol/react-helpers';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers'; import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
const envName = DATA_SOURCES.envName; const envName = DATA_SOURCES.envName;
@ -14,6 +15,7 @@ function App() {
const [theme, toggleTheme] = useThemeSwitcher(); const [theme, toggleTheme] = useThemeSwitcher();
return ( return (
<EnvironmentProvider>
<ThemeContext.Provider value={theme}> <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="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"> <div className="layout-grid w-screen justify-self-center">
@ -27,6 +29,7 @@ function App() {
</div> </div>
</div> </div>
</ThemeContext.Provider> </ThemeContext.Provider>
</EnvironmentProvider>
); );
} }

View File

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

View File

@ -20,12 +20,14 @@ import { Web3Provider } from '@vegaprotocol/web3';
import { Connectors } from './lib/web3-connectors'; import { Connectors } from './lib/web3-connectors';
import { VegaWalletDialogs } from './components/vega-wallet-dialogs'; import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
import { VegaWalletProvider } from '@vegaprotocol/wallet'; import { VegaWalletProvider } from '@vegaprotocol/wallet';
import { EnvironmentProvider } from '@vegaprotocol/react-helpers';
function App() { function App() {
const sideBar = React.useMemo(() => [<EthWallet />, <VegaWallet />], []); const sideBar = React.useMemo(() => [<EthWallet />, <VegaWallet />], []);
return ( return (
<GraphQlProvider> <GraphQlProvider>
<Router> <Router>
<EnvironmentProvider>
<AppStateProvider> <AppStateProvider>
<Web3Provider connectors={Connectors}> <Web3Provider connectors={Connectors}>
<Web3Connector> <Web3Connector>
@ -51,6 +53,7 @@ function App() {
</Web3Connector> </Web3Connector>
</Web3Provider> </Web3Provider>
</AppStateProvider> </AppStateProvider>
</EnvironmentProvider>
</Router> </Router>
</GraphQlProvider> </GraphQlProvider>
); );

View File

@ -1,12 +1,13 @@
import { useTranslation } from 'react-i18next'; 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 { useAddAssetSupported } from '../../hooks/use-add-asset-to-wallet';
import vegaVesting from '../../images/vega_vesting.png'; import vegaVesting from '../../images/vega_vesting.png';
import { AddTokenButtonLink } from '../add-token-button/add-token-button'; import { AddTokenButtonLink } from '../add-token-button/add-token-button';
import { Callout } from '@vegaprotocol/ui-toolkit'; import { Callout } from '@vegaprotocol/ui-toolkit';
export const AddLockedTokenAddress = () => { export const AddLockedTokenAddress = () => {
const { ADDRESSES } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const addSupported = useAddAssetSupported(); const addSupported = useAddAssetSupported();
return ( return (

View File

@ -2,7 +2,7 @@ import * as Sentry from '@sentry/react';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import React from 'react'; import React from 'react';
import { ADDRESSES } from '../../config'; import { useEnvironment } from '@vegaprotocol/react-helpers';
import { import {
AppStateActionType, AppStateActionType,
useAppState, useAppState,
@ -17,6 +17,7 @@ interface BalanceManagerProps {
} }
export const BalanceManager = ({ children }: BalanceManagerProps) => { export const BalanceManager = ({ children }: BalanceManagerProps) => {
const { ADDRESSES } = useEnvironment();
const contracts = useContracts(); const contracts = useContracts();
const { account } = useWeb3React(); const { account } = useWeb3React();
const { appDispatch } = useAppState(); const { appDispatch } = useAppState();
@ -55,7 +56,13 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
}; };
updateBalances(); 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. // This use effect hook is very expensive and is kept separate to prevent expensive reloading of data.
React.useEffect(() => { React.useEffect(() => {

View File

@ -3,7 +3,8 @@ import { useTranslation } from 'react-i18next';
import type { TransactionState } from '../../hooks/transaction-reducer'; import type { TransactionState } from '../../hooks/transaction-reducer';
import { TxState } from '../../hooks/transaction-reducer'; import { TxState } from '../../hooks/transaction-reducer';
import { truncateMiddle } from '../../lib/truncate-middle'; 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 { Error, HandUp, Tick } from '../icons';
import { Loader } from '../loader'; import { Loader } from '../loader';
import { StatefulButton } from '../stateful-button'; import { StatefulButton } from '../stateful-button';
@ -122,6 +123,7 @@ export const TransactionButtonFooter = ({
txHash, txHash,
message, message,
}: TransactionButtonFooterProps) => { }: TransactionButtonFooterProps) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
if (message) { if (message) {
@ -142,7 +144,12 @@ export const TransactionButtonFooter = ({
<div className="transaction-button__footer"> <div className="transaction-button__footer">
<p className="flex justify-between items-start m-0 text-ui"> <p className="flex justify-between items-start m-0 text-ui">
<span>{t('transaction')}</span> <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> </p>
</div> </div>
); );

View File

@ -1,6 +1,7 @@
import { Callout, Intent } from '@vegaprotocol/ui-toolkit'; import { Callout, Intent } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next'; 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'; import type { ReactElement } from 'react';
export const TransactionComplete = ({ export const TransactionComplete = ({
@ -14,6 +15,7 @@ export const TransactionComplete = ({
footer?: ReactElement | string; footer?: ReactElement | string;
body?: ReactElement | string; body?: ReactElement | string;
}) => { }) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Callout <Callout
@ -23,7 +25,12 @@ export const TransactionComplete = ({
> >
{body && <p data-testid="transaction-complete-body">{body}</p>} {body && <p data-testid="transaction-complete-body">{body}</p>}
<p> <p>
<EtherscanLink tx={hash} /> <Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${hash}`}
>
{hash}
</Link>
</p> </p>
{footer && <p data-testid="transaction-complete-footer">{footer}</p>} {footer && <p data-testid="transaction-complete-footer">{footer}</p>}
</Callout> </Callout>

View File

@ -1,8 +1,8 @@
import { Button, Callout, Intent } from '@vegaprotocol/ui-toolkit'; import { Button, Callout, Intent } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { EtherscanLink } from '@vegaprotocol/ui-toolkit'; import { Link } from '@vegaprotocol/ui-toolkit';
import type { Error } from '../icons'; import { useEnvironment } from '@vegaprotocol/react-helpers';
export interface TransactionErrorProps { export interface TransactionErrorProps {
error: Error | null; error: Error | null;
@ -15,6 +15,7 @@ export const TransactionError = ({
hash, hash,
onActionClick, onActionClick,
}: TransactionErrorProps) => { }: TransactionErrorProps) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -22,7 +23,12 @@ export const TransactionError = ({
<p>{error ? error.message : t('Something went wrong')}</p> <p>{error ? error.message : t('Something went wrong')}</p>
{hash ? ( {hash ? (
<p> <p>
<EtherscanLink tx={hash} /> <Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${hash}`}
>
{hash}
</Link>
</p> </p>
) : null} ) : null}
<Button onClick={() => onActionClick()}>{t('Try again')}</Button> <Button onClick={() => onActionClick()}>{t('Try again')}</Button>

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import { Callout } from '@vegaprotocol/ui-toolkit'; import { Callout } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next'; 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 = ({ export const TransactionPending = ({
hash, hash,
@ -18,6 +19,7 @@ export const TransactionPending = ({
footer?: React.ReactElement | string; footer?: React.ReactElement | string;
body?: React.ReactElement | string; body?: React.ReactElement | string;
}) => { }) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const remainingConfirmations = React.useMemo(() => { const remainingConfirmations = React.useMemo(() => {
if (requiredConfirmations) { if (requiredConfirmations) {
@ -38,7 +40,12 @@ export const TransactionPending = ({
<Callout iconName="refresh" title={title}> <Callout iconName="refresh" title={title}>
{body && <p data-testid="transaction-pending-body">{body}</p>} {body && <p data-testid="transaction-pending-body">{body}</p>}
<p> <p>
<EtherscanLink tx={hash} /> <Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${hash}`}
>
{hash}
</Link>
</p> </p>
{footer && <p data-testid="transaction-pending-footer">{footer}</p>} {footer && <p data-testid="transaction-pending-footer">{footer}</p>}
</Callout> </Callout>

View File

@ -1,5 +1,6 @@
import type { TxData } from '@vegaprotocol/smart-contracts'; 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 React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -28,6 +29,7 @@ const TransactionModalStatus = ({
}) => <span className="flex gap-4 items-center">{children}</span>; }) => <span className="flex gap-4 items-center">{children}</span>;
export const TransactionModal = () => { export const TransactionModal = () => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { transactions } = useContracts(); const { transactions } = useContracts();
const { appState, appDispatch } = useAppState(); const { appState, appDispatch } = useAppState();
@ -76,16 +78,20 @@ export const TransactionModal = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{transactions.map((t) => { {transactions.map((transaction) => {
return ( return (
<tr key={t.tx.hash}> <tr key={transaction.tx.hash}>
<TransactionModalTd> <TransactionModalTd>
<EtherscanLink <Link
tx={t.tx.hash} title={t('View transaction on Etherscan')}
text={truncateMiddle(t.tx.hash)} href={`${ETHERSCAN_URL}/tx/${transaction.tx.hash}`}
/> >
{truncateMiddle(transaction.tx.hash)}
</Link>
</TransactionModalTd>
<TransactionModalTd>
{renderStatus(transaction)}
</TransactionModalTd> </TransactionModalTd>
<TransactionModalTd>{renderStatus(t)}</TransactionModalTd>
</tr> </tr>
); );
})} })}

View File

@ -6,7 +6,6 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { AccountType } from '../../__generated__/globalTypes'; import { AccountType } from '../../__generated__/globalTypes';
import { ADDRESSES } from '../../config';
import noIcon from '../../images/token-no-icon.png'; import noIcon from '../../images/token-no-icon.png';
import vegaBlack from '../../images/vega_black.png'; import vegaBlack from '../../images/vega_black.png';
import { BigNumber } from '../../lib/bignumber'; import { BigNumber } from '../../lib/bignumber';
@ -18,6 +17,7 @@ import type {
DelegationsVariables, DelegationsVariables,
} from './__generated__/Delegations'; } from './__generated__/Delegations';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { useEnvironment } from '@vegaprotocol/react-helpers';
const DELEGATIONS_QUERY = gql` const DELEGATIONS_QUERY = gql`
query Delegations($partyId: ID!) { query Delegations($partyId: ID!) {
@ -60,6 +60,7 @@ const DELEGATIONS_QUERY = gql`
`; `;
export const usePollForDelegations = () => { export const usePollForDelegations = () => {
const { ADDRESSES } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { keypair } = useVegaWallet(); const { keypair } = useVegaWallet();
const client = useApolloClient(); const client = useApolloClient();
@ -227,7 +228,7 @@ export const usePollForDelegations = () => {
clearInterval(interval); clearInterval(interval);
mounted = false; mounted = false;
}; };
}, [client, keypair?.pub, t]); }, [client, keypair?.pub, t, ADDRESSES.vegaTokenAddress]);
return { delegations, currentStakeAvailable, delegatedNodes, accounts }; 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 './flags';
export * from './ethereum';
export * from './links'; export * from './links';
export * from './network-params'; export * from './network-params';
export * from './vega'; export * from './vega';

View File

@ -1,11 +1,4 @@
export enum Networks { import { Networks } from '@vegaprotocol/smart-contracts';
CUSTOM = 'CUSTOM',
TESTNET = 'TESTNET',
STAGNET = 'STAGNET',
STAGNET2 = 'STAGNET2',
DEVNET = 'DEVNET',
MAINNET = 'MAINNET',
}
interface VegaNode { interface VegaNode {
url: string; url: string;

View File

@ -10,9 +10,9 @@ import { Splash } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import uniqBy from 'lodash/uniqBy'; import uniqBy from 'lodash/uniqBy';
import React from 'react'; import React from 'react';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import { SplashLoader } from '../../components/splash-loader'; import { SplashLoader } from '../../components/splash-loader';
import { ADDRESSES, APP_ENV } from '../../config';
import type { ContractsContextShape } from './contracts-context'; import type { ContractsContextShape } from './contracts-context';
import { ContractsContext } from './contracts-context'; import { ContractsContext } from './contracts-context';
import { defaultProvider } from '../../lib/web3-connectors'; import { defaultProvider } from '../../lib/web3-connectors';
@ -21,6 +21,7 @@ import { defaultProvider } from '../../lib/web3-connectors';
* Provides Vega Ethereum contract instances to its children. * Provides Vega Ethereum contract instances to its children.
*/ */
export const ContractsProvider = ({ children }: { children: JSX.Element }) => { export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
const { ADDRESSES, VEGA_ENV } = useEnvironment();
const { provider: activeProvider, account } = useWeb3React(); const { provider: activeProvider, account } = useWeb3React();
const [txs, setTxs] = React.useState<TxData[]>([]); const [txs, setTxs] = React.useState<TxData[]>([]);
const [contracts, setContracts] = React.useState<Pick< const [contracts, setContracts] = React.useState<Pick<
@ -53,20 +54,20 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
signer signer
), ),
// @ts-ignore Cant accept JsonRpcProvider provider // @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 // @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 // @ts-ignore Cant accept JsonRpcProvider provider
claim: new VegaClaim(APP_ENV, provider, signer), claim: new VegaClaim(VEGA_ENV, provider, signer),
erc20Bridge: new VegaErc20Bridge( erc20Bridge: new VegaErc20Bridge(
APP_ENV, VEGA_ENV,
// @ts-ignore Cant accept JsonRpcProvider provider // @ts-ignore Cant accept JsonRpcProvider provider
provider, provider,
signer signer
), ),
}); });
} }
}, [activeProvider, account]); }, [activeProvider, account, ADDRESSES.vegaTokenAddress, VEGA_ENV]);
React.useEffect(() => { React.useEffect(() => {
if (!contracts) return; if (!contracts) return;

View File

@ -1,9 +1,9 @@
import React from 'react';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import { MetaMask } from '@web3-react/metamask'; import { MetaMask } from '@web3-react/metamask';
import React from 'react'; import { useEnvironment } from '@vegaprotocol/react-helpers';
import { Networks } from '@vegaprotocol/smart-contracts';
import { APP_ENV, Networks } from '../config';
export const useAddAssetSupported = () => { export const useAddAssetSupported = () => {
const { connector } = useWeb3React(); const { connector } = useWeb3React();
@ -19,6 +19,7 @@ export const useAddAssetToWallet = (
decimals: number, decimals: number,
image: string image: string
) => { ) => {
const { VEGA_ENV } = useEnvironment();
const { provider } = useWeb3React(); const { provider } = useWeb3React();
const addSupported = useAddAssetSupported(); const addSupported = useAddAssetSupported();
const add = React.useCallback(async () => { const add = React.useCallback(async () => {
@ -35,10 +36,10 @@ export const useAddAssetToWallet = (
address, address,
symbol: `${symbol}${ symbol: `${symbol}${
// Add the environment if not mainnet // Add the environment if not mainnet
APP_ENV === Networks.MAINNET VEGA_ENV === Networks.MAINNET
? '' ? ''
: // Remove NET as VEGA(TESTNET) is too long : // Remove NET as VEGA(TESTNET) is too long
` ${APP_ENV.replace('NET', '')}` ` ${VEGA_ENV.replace('NET', '')}`
}`, }`,
decimals, decimals,
image, image,
@ -48,7 +49,7 @@ export const useAddAssetToWallet = (
} catch (error) { } catch (error) {
Sentry.captureException(error); Sentry.captureException(error);
} }
}, [address, decimals, image, provider, symbol]); }, [address, decimals, image, provider, symbol, VEGA_ENV]);
return React.useMemo(() => { return React.useMemo(() => {
return { return {

View File

@ -2,7 +2,7 @@ import * as Sentry from '@sentry/react';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import React from 'react'; import React from 'react';
import { ADDRESSES } from '../config'; import { useEnvironment } from '@vegaprotocol/react-helpers';
import { import {
AppStateActionType, AppStateActionType,
useAppState, useAppState,
@ -10,6 +10,7 @@ import {
import { useContracts } from '../contexts/contracts/contracts-context'; import { useContracts } from '../contexts/contracts/contracts-context';
export const useRefreshBalances = (address: string) => { export const useRefreshBalances = (address: string) => {
const { ADDRESSES } = useEnvironment();
const { appDispatch } = useAppState(); const { appDispatch } = useAppState();
const { keypair } = useVegaWallet(); const { keypair } = useVegaWallet();
const { token, staking, vesting } = useContracts(); const { token, staking, vesting } = useContracts();
@ -44,5 +45,13 @@ export const useRefreshBalances = (address: string) => {
} catch (err) { } catch (err) {
Sentry.captureException(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 { useFetch } from '@vegaprotocol/react-helpers';
import type { Networks, Tranche } from '@vegaprotocol/smart-contracts'; import type { Networks, Tranche } from '@vegaprotocol/smart-contracts';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { APP_ENV } from '../config'; import { useEnvironment } from '@vegaprotocol/react-helpers';
import { BigNumber } from '../lib/bignumber'; import { BigNumber } from '../lib/bignumber';
@ -15,8 +15,9 @@ const TRANCHES_URLS: { [N in Networks]: string } = {
}; };
export function useTranches() { export function useTranches() {
const { VEGA_ENV } = useEnvironment();
const [tranches, setTranches] = React.useState<Tranche[] | null>(null); 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 { const {
state: { data, loading, error }, state: { data, loading, error },
} = useFetch<Tranche[] | null>(url); } = useFetch<Tranche[] | null>(url);

View File

@ -1,11 +1,7 @@
import { import { Callout, Intent, Link, Button } from '@vegaprotocol/ui-toolkit';
Callout, import { useEnvironment } from '@vegaprotocol/react-helpers';
Intent,
EtherscanLink,
Button,
} from '@vegaprotocol/ui-toolkit';
import { Trans, useTranslation } from 'react-i18next'; 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 type { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number'; import { formatNumber } from '../../lib/format-number';
@ -22,6 +18,7 @@ export const Complete = ({
commitTxHash: string | null; commitTxHash: string | null;
claimTxHash: string | null; claimTxHash: string | null;
}) => { }) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -38,18 +35,28 @@ export const Complete = ({
{commitTxHash && ( {commitTxHash && (
<p style={{ margin: 0 }}> <p style={{ margin: 0 }}>
{t('Link transaction')}:{' '} {t('Link transaction')}:{' '}
<EtherscanLink tx={commitTxHash} text={commitTxHash} /> <Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${commitTxHash}`}
>
{commitTxHash}
</Link>
</p> </p>
)} )}
{claimTxHash && ( {claimTxHash && (
<p> <p>
{t('Claim transaction')}:{' '} {t('Claim transaction')}:{' '}
<EtherscanLink tx={claimTxHash} text={claimTxHash} /> <Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${claimTxHash}`}
>
{claimTxHash}
</Link>
</p> </p>
)} )}
<Link to={Routes.VESTING}> <RouteLink to={Routes.VESTING}>
<Button className="fill">{t('Check your vesting VEGA tokens')}</Button> <Button className="fill">{t('Check your vesting VEGA tokens')}</Button>
</Link> </RouteLink>
</Callout> </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 { Heading } from '../../components/heading';
import { ADDRESSES } from '../../config';
const Contracts = () => { const Contracts = () => {
const { ADDRESSES, ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation();
return ( return (
<section> <section>
<Heading title={'Contracts'} /> <Heading title={'Contracts'} />
@ -10,7 +13,12 @@ const Contracts = () => {
{Object.entries(ADDRESSES).map(([key, value]) => ( {Object.entries(ADDRESSES).map(([key, value]) => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>{key}:</div> <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> </div>
))} ))}
</section> </section>

View File

@ -1,13 +1,8 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import { Callout, Link, Intent, Splash } from '@vegaprotocol/ui-toolkit';
Callout, import { useEnvironment } from '@vegaprotocol/react-helpers';
EtherscanLink,
Intent,
Splash,
} from '@vegaprotocol/ui-toolkit';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { ADDRESSES } from '../../../config';
import { useTranches } from '../../../hooks/use-tranches'; import { useTranches } from '../../../hooks/use-tranches';
import type { BigNumber } from '../../../lib/bignumber'; import type { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number'; import { formatNumber } from '../../../lib/format-number';
@ -21,6 +16,7 @@ export const TokenDetails = ({
totalSupply: BigNumber; totalSupply: BigNumber;
totalStaked: BigNumber; totalStaked: BigNumber;
}) => { }) => {
const { ADDRESSES, ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { tranches, loading, error } = useTranches(); const { tranches, loading, error } = useTranches();
@ -45,21 +41,25 @@ export const TokenDetails = ({
<KeyValueTable className={'token-details'}> <KeyValueTable className={'token-details'}>
<KeyValueTableRow> <KeyValueTableRow>
{t('Token address').toUpperCase()} {t('Token address').toUpperCase()}
<EtherscanLink <Link
data-testid="token-address" data-testid="token-address"
address={ADDRESSES.vegaTokenAddress} title={t('View address on Etherscan')}
text={ADDRESSES.vegaTokenAddress}
className="font-mono" className="font-mono"
/> href={`${ETHERSCAN_URL}/address/${ADDRESSES.vegaTokenAddress}`}
>
{ADDRESSES.vegaTokenAddress}
</Link>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
{t('Vesting contract'.toUpperCase())} {t('Vesting contract'.toUpperCase())}
<EtherscanLink <Link
data-testid="token-contract" data-testid="token-contract"
address={ADDRESSES.vestingAddress} title={t('View address on Etherscan')}
text={ADDRESSES.vestingAddress}
className="font-mono" className="font-mono"
/> href={`${ETHERSCAN_URL}/address/${ADDRESSES.vestingAddress}`}
>
{ADDRESSES.vestingAddress}
</Link>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
{t('Total supply').toUpperCase()} {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 { Link, useParams, useOutletContext } from 'react-router-dom';
import { TransactionCallout } from '../../../components/transaction-callout'; 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 { useAppState } from '../../../contexts/app-state/app-state-context';
import { useContracts } from '../../../contexts/contracts/contracts-context'; import { useContracts } from '../../../contexts/contracts/contracts-context';
import { import {
@ -25,6 +25,7 @@ export const RedeemFromTranche = () => {
address: string; address: string;
}>(); }>();
const { vesting } = useContracts(); const { vesting } = useContracts();
const { ADDRESSES } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
appState: { lien, totalVestedBalance, trancheBalances, totalLockedBalance }, appState: { lien, totalVestedBalance, trancheBalances, totalLockedBalance },

View File

@ -1,12 +1,8 @@
import { import { Button, Callout, Link, Intent } from '@vegaprotocol/ui-toolkit';
Button, import { useEnvironment } from '@vegaprotocol/react-helpers';
Callout,
EtherscanLink,
Intent,
} from '@vegaprotocol/ui-toolkit';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; 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 { TransactionCallout } from '../../../components/transaction-callout';
import type { import type {
@ -35,6 +31,7 @@ export const AssociateTransaction = ({
requiredConfirmations: number; requiredConfirmations: number;
linking: PartyStakeLinkings_party_stake_linkings | null; linking: PartyStakeLinkings_party_stake_linkings | null;
}) => { }) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const remainingConfirmations = React.useMemo(() => { const remainingConfirmations = React.useMemo(() => {
@ -71,7 +68,12 @@ export const AssociateTransaction = ({
})} })}
</p> </p>
<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>
<p data-testid="transaction-pending-footer"> <p data-testid="transaction-pending-footer">
{t('pendingAssociationText', { {t('pendingAssociationText', {
@ -90,11 +92,11 @@ export const AssociateTransaction = ({
{ vegaKey } { vegaKey }
)} )}
completeFooter={ completeFooter={
<Link to={Routes.STAKING}> <RouteLink to={Routes.STAKING}>
<Button className="fill"> <Button className="fill">
{t('Nominate Stake to Validator Node')} {t('Nominate Stake to Validator Node')}
</Button> </Button>
</Link> </RouteLink>
} }
pendingHeading={t('Associating Tokens')} pendingHeading={t('Associating Tokens')}
pendingBody={t( pendingBody={t(

View File

@ -2,7 +2,6 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { TokenInput } from '../../../components/token-input'; import { TokenInput } from '../../../components/token-input';
import { ADDRESSES } from '../../../config';
import { import {
AppStateActionType, AppStateActionType,
useAppState, useAppState,
@ -13,6 +12,7 @@ import { useTransaction } from '../../../hooks/use-transaction';
import { BigNumber } from '../../../lib/bignumber'; import { BigNumber } from '../../../lib/bignumber';
import { AssociateInfo } from './associate-info'; import { AssociateInfo } from './associate-info';
import type { VegaKeyExtended } from '@vegaprotocol/wallet'; import type { VegaKeyExtended } from '@vegaprotocol/wallet';
import { useEnvironment } from '@vegaprotocol/react-helpers';
export const WalletAssociate = ({ export const WalletAssociate = ({
perform, perform,
@ -27,6 +27,7 @@ export const WalletAssociate = ({
vegaKey: VegaKeyExtended; vegaKey: VegaKeyExtended;
address: string; address: string;
}) => { }) => {
const { ADDRESSES } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
appDispatch, appDispatch,
@ -56,7 +57,13 @@ export const WalletAssociate = ({
} }
}; };
run(); run();
}, [address, appDispatch, approveState.txState, token]); }, [
address,
appDispatch,
approveState.txState,
token,
ADDRESSES.stakingBridge,
]);
let pageContent = null; let pageContent = null;

View File

@ -1,10 +1,11 @@
import { Button, Callout, Intent } from '@vegaprotocol/ui-toolkit'; import { Button, Callout, Intent } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import { Trans, useTranslation } from 'react-i18next'; 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 { 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 { Links } from '../../config';
import { import {
AppStateActionType, AppStateActionType,
@ -29,14 +30,9 @@ export const Staking = ({ data }: { data?: StakingQueryResult }) => {
<p className="mb-12">{t('stakingDescription3')}</p> <p className="mb-12">{t('stakingDescription3')}</p>
<p className="mb-12">{t('stakingDescription4')}</p> <p className="mb-12">{t('stakingDescription4')}</p>
<p className="mb-12"> <p className="mb-12">
<a <Link href={Links.STAKING_GUIDE} target="_blank">
className="underline"
href={Links.STAKING_GUIDE}
target="_blank"
rel="noreferrer"
>
{t('readMoreStaking')} {t('readMoreStaking')}
</a> </Link>
</p> </p>
</section> </section>
@ -65,6 +61,7 @@ export const Staking = ({ data }: { data?: StakingQueryResult }) => {
}; };
export const StakingStepConnectWallets = () => { export const StakingStepConnectWallets = () => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { account } = useWeb3React(); const { account } = useWeb3React();
const { keypair } = useVegaWallet(); const { keypair } = useVegaWallet();
@ -75,7 +72,12 @@ export const StakingStepConnectWallets = () => {
<Callout intent={Intent.Success} iconName="tick" title={'Connected'}> <Callout intent={Intent.Success} iconName="tick" title={'Connected'}>
<p> <p>
{t('Connected Ethereum address')}&nbsp; {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>
<p> <p>
{t('stakingVegaWalletConnected', { {t('stakingVegaWalletConnected', {
@ -94,7 +96,7 @@ export const StakingStepConnectWallets = () => {
components={{ components={{
vegaWalletLink: ( vegaWalletLink: (
// eslint-disable-next-line jsx-a11y/anchor-has-content // 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) })} title={t('stakingHasAssociated', { tokens: formatNumber(associated) })}
> >
<p> <p>
<Link to="/staking/associate"> <RouteLink to="/staking/associate">
<Button data-testid="associate-more-tokens-btn"> <Button data-testid="associate-more-tokens-btn">
{t('stakingAssociateMoreButton')} {t('stakingAssociateMoreButton')}
</Button> </Button>
</Link> </RouteLink>
</p> </p>
<Link to="/staking/disassociate"> <RouteLink to="/staking/disassociate">
<Button data-testid="disassociate-tokens-btn"> <Button data-testid="disassociate-tokens-btn">
{t('stakingDisassociateButton')} {t('stakingDisassociateButton')}
</Button> </Button>
</Link> </RouteLink>
</Callout> </Callout>
); );
} }
@ -189,11 +191,11 @@ export const StakingStepAssociate = ({
return ( return (
<> <>
<p>{t('stakingStep2Text')}</p> <p>{t('stakingStep2Text')}</p>
<Link to="/staking/associate"> <RouteLink to="/staking/associate">
<Button data-testid="associate-tokens-btn"> <Button data-testid="associate-tokens-btn">
{t('associateButton')} {t('associateButton')}
</Button> </Button>
</Link> </RouteLink>
</> </>
); );
}; };

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; 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 { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { BigNumber } from '../../lib/bignumber'; import { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number'; import { formatNumber } from '../../lib/format-number';
@ -22,6 +23,7 @@ export const ValidatorTable = ({
stakedTotal, stakedTotal,
stakeThisEpoch, stakeThisEpoch,
}: ValidatorTableProps) => { }: ValidatorTableProps) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const stakePercentage = React.useMemo(() => { const stakePercentage = React.useMemo(() => {
const total = new BigNumber(stakedTotal); const total = new BigNumber(stakedTotal);
@ -56,10 +58,12 @@ export const ValidatorTable = ({
<KeyValueTableRow> <KeyValueTableRow>
<span>{t('ETHEREUM ADDRESS')}</span> <span>{t('ETHEREUM ADDRESS')}</span>
<span> <span>
<EtherscanLink <Link
text={node.ethereumAdddress} title={t('View address on Etherscan')}
address={node.ethereumAdddress} href={`${ETHERSCAN_URL}/address/${node.ethereumAdddress}`}
/> >
{node.ethereumAdddress}
</Link>
</span> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>

View File

@ -1,6 +1,10 @@
import { render } from '@testing-library/react'; 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 type { TrancheLabelProps } from './tranche-label';
import { TrancheLabel } from './tranche-label'; import { TrancheLabel } from './tranche-label';
@ -9,7 +13,7 @@ let props: TrancheLabelProps;
beforeEach(() => { beforeEach(() => {
props = { props = {
chainId: EthereumChainIds.Mainnet, chainId: EthereumChainIds.Mainnet,
contract: ADDRESSES.vestingAddress, contract: EnvironmentConfig[Networks.MAINNET].vestingAddress,
id: 5, id: 5,
}; };
}); });

View File

@ -1,5 +1,6 @@
import { ADDRESSES, EthereumChainIds } from '../../config'; import type { EthereumChainId } from '@vegaprotocol/smart-contracts';
import type { EthereumChainId } from '../../config'; import { EthereumChainIds } from '@vegaprotocol/smart-contracts';
import { useEnvironment } from '@vegaprotocol/react-helpers';
const TRANCHE_LABELS: Record<number, string[]> = { const TRANCHE_LABELS: Record<number, string[]> = {
'5': ['Coinlist Option 1', 'Community Whitelist'], '5': ['Coinlist Option 1', 'Community Whitelist'],
@ -26,6 +27,7 @@ export interface TrancheLabelProps {
* @param id The tranche ID on this contract * @param id The tranche ID on this contract
*/ */
export const TrancheLabel = ({ contract, chainId, id }: TrancheLabelProps) => { export const TrancheLabel = ({ contract, chainId, id }: TrancheLabelProps) => {
const { ADDRESSES } = useEnvironment();
// Only mainnet tranches on the known vesting contract have useful name // Only mainnet tranches on the known vesting contract have useful name
if ( if (
chainId && 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 { useWeb3React } from '@web3-react/core';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -6,9 +9,8 @@ import { useParams } from 'react-router';
import { Navigate } from 'react-router-dom'; import { Navigate } from 'react-router-dom';
import { useOutletContext } from 'react-router-dom'; import { useOutletContext } from 'react-router-dom';
import { EtherscanLink } from '@vegaprotocol/ui-toolkit'; import { Link } from '@vegaprotocol/ui-toolkit';
import type { EthereumChainId } from '../../config'; import { useEnvironment } from '@vegaprotocol/react-helpers';
import { ADDRESSES } from '../../config';
import { BigNumber } from '../../lib/bignumber'; import { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number'; import { formatNumber } from '../../lib/format-number';
import { TrancheItem } from '../redemption/tranche-item'; import { TrancheItem } from '../redemption/tranche-item';
@ -27,6 +29,7 @@ const TrancheProgressContents = ({
export const Tranche = () => { export const Tranche = () => {
const tranches = useOutletContext<ITranche[]>(); const tranches = useOutletContext<ITranche[]>();
const { ADDRESSES, ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { trancheId } = useParams<{ trancheId: string }>(); const { trancheId } = useParams<{ trancheId: string }>();
const { chainId } = useWeb3React(); const { chainId } = useWeb3React();
@ -81,7 +84,12 @@ export const Tranche = () => {
const locked = user.remaining_tokens.times(lockedData?.locked || 0); const locked = user.remaining_tokens.times(lockedData?.locked || 0);
return ( return (
<li className="pb-4" key={i}> <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> <TrancheProgressContents>
<span>{t('Locked')}</span> <span>{t('Locked')}</span>
<span>{t('Unlocked')}</span> <span>{t('Unlocked')}</span>

View File

@ -1,15 +1,14 @@
import { useOutletContext } from 'react-router-dom'; 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 { useWeb3React } from '@web3-react/core';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { EthereumChainId } from '../../config';
import { ADDRESSES } from '../../config';
import { TrancheItem } from '../redemption/tranche-item'; import { TrancheItem } from '../redemption/tranche-item';
import { TrancheLabel } from './tranche-label'; import { TrancheLabel } from './tranche-label';
import { VestingChart } from './vesting-chart'; import { VestingChart } from './vesting-chart';
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/react-helpers';
const trancheMinimum = 10; const trancheMinimum = 10;
@ -17,6 +16,7 @@ const shouldShowTranche = (t: Tranche) =>
!t.total_added.isLessThanOrEqualTo(trancheMinimum); !t.total_added.isLessThanOrEqualTo(trancheMinimum);
export const Tranches = () => { export const Tranches = () => {
const { ADDRESSES } = useEnvironment();
const tranches = useOutletContext<Tranche[]>(); const tranches = useOutletContext<Tranche[]>();
const [showAll, setShowAll] = React.useState<boolean>(false); const [showAll, setShowAll] = React.useState<boolean>(false);
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -4,7 +4,8 @@ import orderBy from 'lodash/orderBy';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; 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 { Heading } from '../../components/heading';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { SplashLoader } from '../../components/splash-loader'; import { SplashLoader } from '../../components/splash-loader';
@ -99,6 +100,7 @@ interface WithdrawalProps {
} }
export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => { export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const renderStatus = ({ const renderStatus = ({
@ -148,12 +150,12 @@ export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
<KeyValueTableRow> <KeyValueTableRow>
{t('toEthereum')} {t('toEthereum')}
<span> <span>
<EtherscanLink <Link
address={withdrawal.details?.receiverAddress as string} title={t('View address on Etherscan')}
text={truncateMiddle( href={`${ETHERSCAN_URL}/tx/${withdrawal.details?.receiverAddress}`}
withdrawal.details?.receiverAddress as string >
)} {truncateMiddle(withdrawal.details?.receiverAddress ?? '')}
/> </Link>
</span> </span>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
@ -169,10 +171,12 @@ export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
{t('withdrawalTransaction', { foreignChain: 'Ethereum' })} {t('withdrawalTransaction', { foreignChain: 'Ethereum' })}
<span> <span>
{withdrawal.txHash ? ( {withdrawal.txHash ? (
<EtherscanLink <Link
tx={withdrawal.txHash} title={t('View transaction on Etherscan')}
text={truncateMiddle(withdrawal.txHash)} href={`${ETHERSCAN_URL}/tx/${withdrawal.txHash}`}
/> >
{truncateMiddle(withdrawal.txHash)}
</Link>
) : ( ) : (
'-' '-'
)} )}

View File

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

View File

@ -10,6 +10,7 @@ export const generateDealTicketQuery = (
market: { market: {
id: 'market-id', id: 'market-id',
decimalPlaces: 2, decimalPlaces: 2,
positionDecimalPlaces: 1,
state: MarketState.Active, state: MarketState.Active,
tradingMode: MarketTradingMode.Continuous, tradingMode: MarketTradingMode.Continuous,
tradableInstrument: { 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() { validateConnectWalletText() {
cy.getByTestId(this.connectVegaWalletText).should( cy.getByTestId(this.connectVegaWalletText).should(
'have.text', '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 { Given } from 'cypress-cucumber-preprocessor/steps';
import { hasOperationName } from '..';
import { generateMarketList } from '../mocks/generate-market-list';
import BasePage from '../pages/base-page'; import BasePage from '../pages/base-page';
const basePage = new BasePage(); const basePage = new BasePage();
Given('I am on the homepage', () => { Given('I am on the homepage', () => {
cy.mockGQL('MarketsList', (req) => {
if (hasOperationName(req, 'MarketsList')) {
req.reply({
body: { data: generateMarketList() },
});
}
});
cy.visit('/'); cy.visit('/');
basePage.closeDialog(); basePage.closeDialog();
}); });

View File

@ -11,7 +11,7 @@
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"strict": true, "strict": true,
"noImplicitOverride": true, "noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true, "noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": 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(); const { isActive, error, connector, chainId } = useWeb3React();
useEffect(() => { useEffect(() => {
if ( if (connector?.connectEagerly) {
connector?.connectEagerly &&
// Dont eager connect if this is a cypress test run
'Cypress' in window
) {
connector.connectEagerly(); connector.connectEagerly();
} }
}, [connector]); }, [connector]);

View File

@ -7,24 +7,24 @@ import {
VegaManageDialog, VegaManageDialog,
VegaWalletProvider, VegaWalletProvider,
} from '@vegaprotocol/wallet'; } from '@vegaprotocol/wallet';
import { EnvironmentProvider } from '@vegaprotocol/react-helpers';
import { Connectors } from '../lib/vega-connectors'; import { Connectors } from '../lib/vega-connectors';
import { useMemo, useState } from 'react'; import { useMemo } from 'react';
import { createClient } from '../lib/apollo-client'; import { createClient } from '../lib/apollo-client';
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit'; import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
import { ApolloProvider } from '@apollo/client'; import { ApolloProvider } from '@apollo/client';
import { AppLoader } from '../components/app-loader'; import { AppLoader } from '../components/app-loader';
import { VegaWalletConnectButton } from '../components/vega-wallet-connect-button'; import { VegaWalletConnectButton } from '../components/vega-wallet-connect-button';
import './styles.css'; import './styles.css';
import { useGlobalStore } from '../stores';
function VegaTradingApp({ Component, pageProps }: AppProps) { function VegaTradingApp({ Component, pageProps }: AppProps) {
const client = useMemo(() => createClient(process.env['NX_VEGA_URL']), []); const client = useMemo(() => createClient(process.env['NX_VEGA_URL']), []);
const [vegaWallet, setVegaWallet] = useState({ const store = useGlobalStore();
connect: false,
manage: false,
});
const [theme, toggleTheme] = useThemeSwitcher(); const [theme, toggleTheme] = useThemeSwitcher();
return ( return (
<EnvironmentProvider>
<ThemeContext.Provider value={theme}> <ThemeContext.Provider value={theme}>
<ApolloProvider client={client}> <ApolloProvider client={client}>
<VegaWalletProvider> <VegaWalletProvider>
@ -43,19 +43,22 @@ function VegaTradingApp({ Component, pageProps }: AppProps) {
type="image/x-icon" type="image/x-icon"
href="https://static.vega.xyz/favicon.ico" href="https://static.vega.xyz/favicon.ico"
/> />
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" /> <link
rel="stylesheet"
href="https://static.vega.xyz/fonts.css"
/>
</Head> </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="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"> <div className="flex items-stretch border-b-[7px] border-vega-yellow">
<Navbar /> <Navbar />
<div className="flex items-center gap-4 ml-auto mr-8"> <div className="flex items-center gap-4 ml-auto mr-8">
<VegaWalletConnectButton <VegaWalletConnectButton
setConnectDialog={(open) => setConnectDialog={(open) => {
setVegaWallet((x) => ({ ...x, connect: open })) store.setVegaWalletConnectDialog(open);
} }}
setManageDialog={(open) => setManageDialog={(open) => {
setVegaWallet((x) => ({ ...x, manage: open })) store.setVegaWalletManageDialog(open);
} }}
/> />
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" /> <ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
</div> </div>
@ -66,15 +69,15 @@ function VegaTradingApp({ Component, pageProps }: AppProps) {
</main> </main>
<VegaConnectDialog <VegaConnectDialog
connectors={Connectors} connectors={Connectors}
dialogOpen={vegaWallet.connect} dialogOpen={store.vegaWalletConnectDialog}
setDialogOpen={(open) => setDialogOpen={(open) =>
setVegaWallet((x) => ({ ...x, connect: open })) store.setVegaWalletConnectDialog(open)
} }
/> />
<VegaManageDialog <VegaManageDialog
dialogOpen={vegaWallet.manage} dialogOpen={store.vegaWalletManageDialog}
setDialogOpen={(open) => setDialogOpen={(open) =>
setVegaWallet((x) => ({ ...x, manage: open })) store.setVegaWalletManageDialog(open)
} }
/> />
</div> </div>
@ -82,6 +85,7 @@ function VegaTradingApp({ Component, pageProps }: AppProps) {
</VegaWalletProvider> </VegaWalletProvider>
</ApolloProvider> </ApolloProvider>
</ThemeContext.Provider> </ThemeContext.Provider>
</EnvironmentProvider>
); );
} }

View File

@ -1,9 +1,10 @@
import { gql, useQuery } from '@apollo/client'; import { gql, useQuery } from '@apollo/client';
import { LandingDialog } from '@vegaprotocol/market-list';
import { MarketTradingMode } from '@vegaprotocol/types'; import { MarketTradingMode } from '@vegaprotocol/types';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import sortBy from 'lodash/sortBy'; 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'; import type { MarketsLanding } from './__generated__/MarketsLanding';
const MARKETS_QUERY = gql` const MARKETS_QUERY = gql`
@ -29,24 +30,33 @@ const marketList = ({ markets }: MarketsLanding) =>
); );
export function Index() { export function Index() {
const { replace } = useRouter();
// The default market selected in the platform behind the overlay // 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). // 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); const { data, error, loading } = useQuery<MarketsLanding>(MARKETS_QUERY);
if (data && !error && !loading) { const setLandingDialog = useGlobalStore((state) => state.setLandingDialog);
useEffect(() => {
if (data) {
const marketId = marketList(data)[0]?.id; const marketId = marketList(data)[0]?.id;
window.history.replaceState(
data, // If a default market is found, go to it with the landing dialog open
'', if (marketId) {
marketId ? `/markets/${marketId}` : '/markets' setLandingDialog(true);
); replace(`/markets/${marketId}`);
} }
// Fallback to the markets list page
else {
replace('/markets');
}
}
}, [data, replace, setLandingDialog]);
return ( return (
<> <AsyncRenderer data={data} loading={loading} error={error}>
<LandingDialog /> {/* Render a loading and error state but we will redirect if markets are found */}
<AsyncRenderer data={data} error={error} loading={loading}> {null}
<MarketPage id={data && marketList(data)[0]?.id} />
</AsyncRenderer> </AsyncRenderer>
</>
); );
} }

View File

@ -7,6 +7,8 @@ import debounce from 'lodash/debounce';
import { PageQueryContainer } from '../../components/page-query-container'; import { PageQueryContainer } from '../../components/page-query-container';
import { TradeGrid, TradePanels } from './trade-grid'; import { TradeGrid, TradePanels } from './trade-grid';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import { useGlobalStore } from '../../stores';
import { LandingDialog } from '@vegaprotocol/market-list';
// Top level page query // Top level page query
const MARKET_QUERY = gql` const MARKET_QUERY = gql`
@ -21,6 +23,7 @@ const MARKET_QUERY = gql`
const MarketPage = ({ id }: { id?: string }) => { const MarketPage = ({ id }: { id?: string }) => {
const { query } = useRouter(); const { query } = useRouter();
const { w } = useWindowSize(); const { w } = useWindowSize();
const store = useGlobalStore();
// Default to first marketId query item if found // Default to first marketId query item if found
const marketId = const marketId =
@ -48,10 +51,18 @@ const MarketPage = ({ id }: { id?: string }) => {
return <Splash>{t('Market not found')}</Splash>; return <Splash>{t('Market not found')}</Splash>;
} }
return w > 960 ? ( return (
<>
{w > 960 ? (
<TradeGrid market={market} /> <TradeGrid market={market} />
) : ( ) : (
<TradePanels 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 { PageQueryContainer } from '../../../components/page-query-container';
import type { DepositPage } from './__generated__/DepositPage'; import type { DepositPage } from './__generated__/DepositPage';
import { DepositManager } from '@vegaprotocol/deposits'; 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 { Splash } from '@vegaprotocol/ui-toolkit';
import { ASSET_FRAGMENT } from '../../../lib/query-fragments'; import { ASSET_FRAGMENT } from '../../../lib/query-fragments';
@ -28,6 +28,8 @@ export const DepositContainer = ({
ethereumConfig, ethereumConfig,
assetId, assetId,
}: DepositContainerProps) => { }: DepositContainerProps) => {
const { VEGA_ENV } = useEnvironment();
return ( return (
<PageQueryContainer<DepositPage> <PageQueryContainer<DepositPage>
query={DEPOSIT_PAGE_QUERY} query={DEPOSIT_PAGE_QUERY}
@ -46,6 +48,7 @@ export const DepositContainer = ({
requiredConfirmations={ethereumConfig.confirmations} requiredConfirmations={ethereumConfig.confirmations}
assets={data.assets} assets={data.assets}
initialAssetId={assetId} initialAssetId={assetId}
isFaucetable={VEGA_ENV !== 'MAINNET'}
/> />
); );
}} }}

View File

@ -1,8 +1,9 @@
import { Web3Container } from '../../../components/web3-container';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { WithdrawPageContainer } from './withdraw-page-container'; import { WithdrawPageContainer } from './withdraw-page-container';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import { VegaWalletContainer } from '../../../components/vega-wallet-container';
import { Web3Container } from '../../../components/web3-container';
const Withdraw = () => { const Withdraw = () => {
const { query } = useRouter(); const { query } = useRouter();
@ -21,6 +22,7 @@ const Withdraw = () => {
}, [query]); }, [query]);
return ( return (
<VegaWalletContainer>
<Web3Container <Web3Container
render={() => ( render={() => (
<div className="max-w-[420px] p-24 mx-auto"> <div className="max-w-[420px] p-24 mx-auto">
@ -29,6 +31,7 @@ const Withdraw = () => {
</div> </div>
)} )}
/> />
</VegaWalletContainer>
); );
}; };

View File

@ -47,14 +47,6 @@ export const WithdrawPageContainer = ({
}: WithdrawPageContainerProps) => { }: WithdrawPageContainerProps) => {
const { keypair } = useVegaWallet(); const { keypair } = useVegaWallet();
if (!keypair) {
return (
<p data-testid="connect-vega-wallet-text">
{t('Please connect your Vega wallet')}
</p>
);
}
return ( return (
<PageQueryContainer<WithdrawPageQuery, WithdrawPageQueryVariables> <PageQueryContainer<WithdrawPageQuery, WithdrawPageQueryVariables>
query={WITHDRAW_PAGE_QUERY} query={WITHDRAW_PAGE_QUERY}

View File

@ -1,17 +1,12 @@
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import { AnchorButton, Splash } from '@vegaprotocol/ui-toolkit'; import { AnchorButton } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { VegaWalletContainer } from '../../../components/vega-wallet-container';
import { Web3Container } from '../../../components/web3-container'; import { Web3Container } from '../../../components/web3-container';
import { WithdrawalsPageContainer } from './withdrawals-page-container'; import { WithdrawalsPageContainer } from './withdrawals-page-container';
const Withdrawals = () => { const Withdrawals = () => {
const { keypair } = useVegaWallet();
if (!keypair) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return ( return (
<VegaWalletContainer>
<Web3Container <Web3Container
render={() => ( render={() => (
<div className="h-full grid grid grid-rows-[min-content,1fr]"> <div className="h-full grid grid grid-rows-[min-content,1fr]">
@ -25,6 +20,7 @@ const Withdrawals = () => {
</div> </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, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noEmit": true, "noEmit": true,
"resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"incremental": true "incremental": true
}, },

View File

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

View File

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

View File

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

View File

@ -72,6 +72,12 @@ export interface DealTicketQuery_market {
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/ */
decimalPlaces: number; 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 * 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 { import type {
DealTicketQuery, DealTicketQuery,
DealTicketQuery_market, DealTicketQuery_market,
} from './__generated__/DealTicketQuery'; } from '../__generated__/DealTicketQuery';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
const DEAL_TICKET_QUERY = gql` const DEAL_TICKET_QUERY = gql`
@ -12,6 +12,7 @@ const DEAL_TICKET_QUERY = gql`
market(id: $marketId) { market(id: $marketId) {
id id
decimalPlaces decimalPlaces
positionDecimalPlaces
state state
tradingMode tradingMode
tradableInstrument { tradableInstrument {

View File

@ -1,30 +1,32 @@
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
import { validateSize } from '../utils/validate-size';
import type { DealTicketAmountProps } from './deal-ticket-amount';
export interface DealTicketLimitFormProps { export type DealTicketLimitAmountProps = Omit<
quoteName: string; DealTicketAmountProps,
price?: string; 'orderType'
size: string; >;
onSizeChange: (size: string) => void;
onPriceChange: (price: string) => void;
}
export const DealTicketLimitForm = ({ export const DealTicketLimitAmount = ({
size, register,
price, step,
onSizeChange,
onPriceChange,
quoteName, quoteName,
}: DealTicketLimitFormProps) => { }: DealTicketLimitAmountProps) => {
return ( return (
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
<div className="flex-1"> <div className="flex-1">
<FormGroup label="Amount"> <FormGroup label="Amount">
<Input <Input
value={size}
onChange={(e) => onSizeChange(e.target.value)}
className="w-full" className="w-full"
type="number" type="number"
step={step}
min={step}
data-testid="order-size" data-testid="order-size"
{...register('size', {
required: true,
min: step,
validate: validateSize(step),
})}
/> />
</FormGroup> </FormGroup>
</div> </div>
@ -32,11 +34,12 @@ export const DealTicketLimitForm = ({
<div className="flex-1"> <div className="flex-1">
<FormGroup label={`Price (${quoteName})`} labelAlign="right"> <FormGroup label={`Price (${quoteName})`} labelAlign="right">
<Input <Input
value={price}
onChange={(e) => onPriceChange(e.target.value)}
className="w-full" className="w-full"
type="number" type="number"
step={step}
defaultValue={0}
data-testid="order-price" data-testid="order-price"
{...register('price', { required: true, min: 0 })}
/> />
</FormGroup> </FormGroup>
</div> </div>

View File

@ -4,9 +4,9 @@ import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
import { OrderStatus } from '@vegaprotocol/types'; import { OrderStatus } from '@vegaprotocol/types';
import { VegaTxStatus } from '@vegaprotocol/wallet'; import { VegaTxStatus } from '@vegaprotocol/wallet';
import { DealTicket } from './deal-ticket'; import { DealTicket } from './deal-ticket';
import { useOrderSubmit } from './use-order-submit';
import { OrderDialog } from './order-dialog'; 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 { export interface DealTicketManagerProps {
market: DealTicketQuery_market; market: DealTicketQuery_market;

View File

@ -1,28 +1,33 @@
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
import { validateSize } from '../utils/validate-size';
import type { DealTicketAmountProps } from './deal-ticket-amount';
export interface DealTicketMarketFormProps { export type DealTicketMarketAmountProps = Omit<
quoteName?: string; DealTicketAmountProps,
price?: string; 'orderType'
size: string; >;
onSizeChange: (size: string) => void;
}
export const DealTicketMarketForm = ({ export const DealTicketMarketAmount = ({
size, register,
onSizeChange,
price, price,
step,
quoteName, quoteName,
}: DealTicketMarketFormProps) => { }: DealTicketMarketAmountProps) => {
return ( return (
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
<div className="flex-1"> <div className="flex-1">
<FormGroup label="Amount"> <FormGroup label="Amount">
<Input <Input
value={size}
onChange={(e) => onSizeChange(e.target.value)}
className="w-full" className="w-full"
type="number" type="number"
step={step}
min={step}
data-testid="order-size" data-testid="order-size"
{...register('size', {
required: true,
min: step,
validate: validateSize(step),
})}
/> />
</FormGroup> </FormGroup>
</div> </div>

View File

@ -4,22 +4,17 @@ import {
OrderType, OrderType,
} from '@vegaprotocol/wallet'; } from '@vegaprotocol/wallet';
import { addDecimal } from '@vegaprotocol/react-helpers'; 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 { 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'; import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
const order: Order = {
type: OrderType.Market,
size: '100',
timeInForce: OrderTimeInForce.FOK,
side: null,
};
const market: DealTicketQuery_market = { const market: DealTicketQuery_market = {
__typename: 'Market', __typename: 'Market',
id: 'market-id', id: 'market-id',
decimalPlaces: 2, decimalPlaces: 2,
positionDecimalPlaces: 1,
tradingMode: MarketTradingMode.Continuous, tradingMode: MarketTradingMode.Continuous,
state: MarketState.Active, state: MarketState.Active,
tradableInstrument: { tradableInstrument: {
@ -43,7 +38,7 @@ const market: DealTicketQuery_market = {
const submit = jest.fn(); const submit = jest.fn();
const transactionStatus = 'default'; const transactionStatus = 'default';
function generateJsx() { function generateJsx(order?: Order) {
return ( return (
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
<VegaWalletContext.Provider value={{} as any}> <VegaWalletContext.Provider value={{} as any}>
@ -57,21 +52,23 @@ function generateJsx() {
); );
} }
it('Deal ticket defaults', () => { it('Displays ticket defaults', () => {
render(generateJsx()); render(generateJsx());
// Assert defaults are used // Assert defaults are used
expect( expect(
screen.getByTestId(`order-type-${order.type}-selected`) screen.getByTestId(`order-type-${OrderType.Market}-selected`)
).toBeInTheDocument(); ).toBeInTheDocument();
expect( expect(
screen.queryByTestId('order-side-SIDE_BUY-selected') screen.queryByTestId('order-side-SIDE_BUY-selected')
).not.toBeInTheDocument(); ).toBeInTheDocument();
expect( expect(
screen.queryByTestId('order-side-SIDE_SELL-selected') screen.queryByTestId('order-side-SIDE_SELL-selected')
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
expect(screen.getByTestId('order-size')).toHaveDisplayValue(order.size); expect(screen.getByTestId('order-size')).toHaveDisplayValue(
expect(screen.getByTestId('order-tif')).toHaveValue(order.timeInForce); String(1 / Math.pow(10, market.positionDecimalPlaces))
);
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.IOC);
// Assert last price is shown // Assert last price is shown
expect(screen.getByTestId('last-price')).toHaveTextContent( 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()); render(generateJsx());
// Asssert changing values // BUY is selected by default
fireEvent.click(screen.getByTestId('order-side-SIDE_BUY')); screen.getByTestId('order-side-SIDE_BUY-selected');
expect(
screen.getByTestId('order-side-SIDE_BUY-selected')
).toBeInTheDocument();
await act(async () => {
fireEvent.change(screen.getByTestId('order-size'), { fireEvent.change(screen.getByTestId('order-size'), {
target: { value: '200' }, target: { value: '200' },
}); });
});
expect(screen.getByTestId('order-size')).toHaveDisplayValue('200'); expect(screen.getByTestId('order-size')).toHaveDisplayValue('200');
fireEvent.change(screen.getByTestId('order-tif'), { 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 { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
import type { Order } from './use-order-state';
import { formatForInput } from '@vegaprotocol/react-helpers'; import { formatForInput } from '@vegaprotocol/react-helpers';
interface ExpirySelectorProps { interface ExpirySelectorProps {
order: Order; value?: Date;
onSelect: (expiration: Date | null) => void; onSelect: (expiration: Date | null) => void;
} }
export const ExpirySelector = ({ order, onSelect }: ExpirySelectorProps) => { export const ExpirySelector = ({ value, onSelect }: ExpirySelectorProps) => {
const date = order.expiration ? new Date(order.expiration) : new Date(); const date = value ? new Date(value) : new Date();
const dateFormatted = formatForInput(date); const dateFormatted = formatForInput(date);
const minDate = formatForInput(date); const minDate = formatForInput(date);
return ( return (

View File

@ -1,6 +1,6 @@
import { Icon, Loader } from '@vegaprotocol/ui-toolkit'; import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react'; 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 { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
import type { VegaTxState } from '@vegaprotocol/wallet'; import type { VegaTxState } from '@vegaprotocol/wallet';
import { VegaTxStatus } from '@vegaprotocol/wallet'; import { VegaTxStatus } from '@vegaprotocol/wallet';

View File

@ -1,14 +1,13 @@
import { FormGroup } from '@vegaprotocol/ui-toolkit'; import { FormGroup } from '@vegaprotocol/ui-toolkit';
import { OrderSide } from '@vegaprotocol/wallet'; import { OrderSide } from '@vegaprotocol/wallet';
import { Toggle } from '@vegaprotocol/ui-toolkit'; import { Toggle } from '@vegaprotocol/ui-toolkit';
import type { Order } from './use-order-state';
interface SideSelectorProps { interface SideSelectorProps {
order: Order; value: OrderSide;
onSelect: (side: OrderSide) => void; onSelect: (side: OrderSide) => void;
} }
export const SideSelector = ({ order, onSelect }: SideSelectorProps) => { export const SideSelector = ({ value, onSelect }: SideSelectorProps) => {
const toggles = Object.entries(OrderSide).map(([label, value]) => ({ const toggles = Object.entries(OrderSide).map(([label, value]) => ({
label, label,
value, value,
@ -19,7 +18,7 @@ export const SideSelector = ({ order, onSelect }: SideSelectorProps) => {
<Toggle <Toggle
name="order-side" name="order-side"
toggles={toggles} toggles={toggles}
checkedValue={order.side} checkedValue={value}
onChange={(e) => onSelect(e.target.value as OrderSide)} onChange={(e) => onSelect(e.target.value as OrderSide)}
/> />
</FormGroup> </FormGroup>

View File

@ -1,28 +1,30 @@
import { FormGroup, Select } from '@vegaprotocol/ui-toolkit'; import { FormGroup, Select } from '@vegaprotocol/ui-toolkit';
import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet'; import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
import type { Order } from './use-order-state';
interface TimeInForceSelectorProps { interface TimeInForceSelectorProps {
order: Order; value: OrderTimeInForce;
orderType: OrderType;
onSelect: (tif: OrderTimeInForce) => void; onSelect: (tif: OrderTimeInForce) => void;
} }
export const TimeInForceSelector = ({ export const TimeInForceSelector = ({
order, value,
orderType,
onSelect, onSelect,
}: TimeInForceSelectorProps) => { }: TimeInForceSelectorProps) => {
const options = const options =
order.type === OrderType.Limit orderType === OrderType.Limit
? Object.entries(OrderTimeInForce) ? Object.entries(OrderTimeInForce)
: Object.entries(OrderTimeInForce).filter( : Object.entries(OrderTimeInForce).filter(
([_, value]) => ([_, timeInForce]) =>
value === OrderTimeInForce.FOK || value === OrderTimeInForce.IOC timeInForce === OrderTimeInForce.FOK ||
timeInForce === OrderTimeInForce.IOC
); );
return ( return (
<FormGroup label="Time in force"> <FormGroup label="Time in force">
<Select <Select
value={order.timeInForce} value={value}
onChange={(e) => onSelect(e.target.value as OrderTimeInForce)} onChange={(e) => onSelect(e.target.value as OrderTimeInForce)}
className="w-full" className="w-full"
data-testid="order-tif" data-testid="order-tif"

View File

@ -1,25 +1,24 @@
import { FormGroup } from '@vegaprotocol/ui-toolkit'; import { FormGroup } from '@vegaprotocol/ui-toolkit';
import { OrderType } from '@vegaprotocol/wallet'; import { OrderType } from '@vegaprotocol/wallet';
import { Toggle } from '@vegaprotocol/ui-toolkit'; import { Toggle } from '@vegaprotocol/ui-toolkit';
import type { Order } from './use-order-state';
interface TypeSelectorProps { interface TypeSelectorProps {
order: Order; value: OrderType;
onSelect: (type: OrderType) => void; onSelect: (type: OrderType) => void;
} }
export const TypeSelector = ({ order, onSelect }: TypeSelectorProps) => {
const toggles = Object.entries(OrderType).map(([label, value]) => ({ const toggles = Object.entries(OrderType).map(([label, value]) => ({
label, label,
value, value,
})); }));
export const TypeSelector = ({ value, onSelect }: TypeSelectorProps) => {
return ( return (
<FormGroup label="Order type"> <FormGroup label="Order type">
<Toggle <Toggle
name="order-type" name="order-type"
toggles={toggles} toggles={toggles}
checkedValue={order.type} checkedValue={value}
onChange={(e) => onSelect(e.target.value as OrderType)} onChange={(e) => onSelect(e.target.value as OrderType)}
/> />
</FormGroup> </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 { MockedProvider } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react-hooks'; 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 { import type {
VegaKeyExtended, VegaKeyExtended,
VegaWalletContextShape, VegaWalletContextShape,
} from '@vegaprotocol/wallet'; } from '@vegaprotocol/wallet';
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet'; import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet'; import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useOrderSubmit } from './use-order-submit'; 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 = { const defaultWalletContext = {
keypair: null, keypair: null,
@ -22,7 +50,7 @@ const defaultWalletContext = {
function setup( function setup(
context?: Partial<VegaWalletContextShape>, context?: Partial<VegaWalletContextShape>,
market = { id: 'market-id', decimalPlaces: 2 } market = defaultMarket
) { ) {
const wrapper = ({ children }: { children: ReactNode }) => ( const wrapper = ({ children }: { children: ReactNode }) => (
<MockedProvider> <MockedProvider>
@ -111,20 +139,16 @@ it('Should submit a correctly formatted order', async () => {
const keypair = { const keypair = {
pub: '0x123', pub: '0x123',
} as VegaKeyExtended; } as VegaKeyExtended;
const market = {
id: 'market-id',
decimalPlaces: 2,
};
const { result } = setup( const { result } = setup(
{ {
sendTx: mockSendTx, sendTx: mockSendTx,
keypairs: [keypair], keypairs: [keypair],
keypair, keypair,
}, },
market defaultMarket
); );
const order = { const order: Order = {
type: OrderType.Limit, type: OrderType.Limit,
size: '10', size: '10',
timeInForce: OrderTimeInForce.GTT, timeInForce: OrderTimeInForce.GTT,
@ -141,12 +165,12 @@ it('Should submit a correctly formatted order', async () => {
propagate: true, propagate: true,
orderSubmission: { orderSubmission: {
type: OrderType.Limit, type: OrderType.Limit,
marketId: market.id, // Market provided from hook arugment marketId: defaultMarket.id, // Market provided from hook arugment
size: '10', size: '100', // size adjusted based on positionDecimalPlaces
side: OrderSide.Buy, side: OrderSide.Buy,
timeInForce: OrderTimeInForce.GTT, timeInForce: OrderTimeInForce.GTT,
price: '123456789', // Decimal removed 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 { useCallback, useEffect, useState } from 'react';
import { gql, useSubscription } from '@apollo/client'; 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 { OrderType, useVegaWallet } from '@vegaprotocol/wallet';
import { determineId, removeDecimal } from '@vegaprotocol/react-helpers'; import { determineId, removeDecimal } from '@vegaprotocol/react-helpers';
import { useVegaTransaction } from '@vegaprotocol/wallet'; import { useVegaTransaction } from '@vegaprotocol/wallet';
@ -8,7 +8,8 @@ import type {
OrderEvent, OrderEvent,
OrderEventVariables, OrderEventVariables,
OrderEvent_busEvents_event_Order, OrderEvent_busEvents_event_Order,
} from './__generated__/OrderEvent'; } from '../__generated__/OrderEvent';
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
const ORDER_EVENT_SUB = gql` const ORDER_EVENT_SUB = gql`
subscription OrderEvent($partyId: ID!) { subscription OrderEvent($partyId: ID!) {
@ -35,12 +36,7 @@ const ORDER_EVENT_SUB = gql`
} }
`; `;
interface UseOrderSubmitMarket { export const useOrderSubmit = (market: DealTicketQuery_market) => {
id: string;
decimalPlaces: number;
}
export const useOrderSubmit = (market: UseOrderSubmitMarket) => {
const { keypair } = useVegaWallet(); const { keypair } = useVegaWallet();
const { send, transaction, reset: resetTransaction } = useVegaTransaction(); const { send, transaction, reset: resetTransaction } = useVegaTransaction();
const [id, setId] = useState(''); const [id, setId] = useState('');
@ -97,7 +93,7 @@ export const useOrderSubmit = (market: UseOrderSubmitMarket) => {
order.type === OrderType.Limit && order.price order.type === OrderType.Limit && order.price
? removeDecimal(order.price, market.decimalPlaces) ? removeDecimal(order.price, market.decimalPlaces)
: undefined, : undefined,
size: order.size, size: removeDecimal(order.size, market.positionDecimalPlaces),
type: order.type, type: order.type,
side: order.side, side: order.side,
timeInForce: order.timeInForce, 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 './components/expiry-selector';
export * from './type-selector'; export * from './components/type-selector';
export * from './time-in-force-selector'; export * from './components/time-in-force-selector';
export * from './submit-button'; export * from './components/side-selector';
export * from './side-selector'; export * from './components/deal-ticket';
export * from './deal-ticket'; export * from './components/deal-ticket-amount';
export * from './deal-ticket-limit-form'; export * from './components/deal-ticket-limit-amount';
export * from './deal-ticket-market-form'; export * from './components/deal-ticket-market-amount';
export * from './deal-ticket-manager'; export * from './components/deal-ticket-manager';
export * from './order-dialog'; export * from './components/order-dialog';
export * from './use-order-state'; export * from './components/deal-ticket-container';
export * from './use-order-submit';
export * from './deal-ticket-container';
export * from './__generated__/DealTicketQuery'; export * from './__generated__/DealTicketQuery';
export * from './__generated__/OrderEvent'; 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, "forceConsistentCasingInFileNames": true,
"strict": true, "strict": true,
"noImplicitOverride": true, "noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true, "noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": 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), max: new BigNumber(20),
}, },
allowance: new BigNumber(30), allowance: new BigNumber(30),
isFaucetable: true,
}; };
}); });

View File

@ -24,7 +24,6 @@ import { useMemo } from 'react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useForm, useWatch } from 'react-hook-form'; import { useForm, useWatch } from 'react-hook-form';
import { DepositLimits } from './deposit-limits'; import { DepositLimits } from './deposit-limits';
import { FAUCETABLE } from '../config';
import type { Asset } from './deposit-manager'; import type { Asset } from './deposit-manager';
interface FormFields { interface FormFields {
@ -51,6 +50,7 @@ export interface DepositFormProps {
max: BigNumber; max: BigNumber;
} | null; } | null;
allowance: BigNumber | undefined; allowance: BigNumber | undefined;
isFaucetable?: boolean;
} }
export const DepositForm = ({ export const DepositForm = ({
@ -63,6 +63,7 @@ export const DepositForm = ({
requestFaucet, requestFaucet,
limits, limits,
allowance, allowance,
isFaucetable,
}: DepositFormProps) => { }: DepositFormProps) => {
const { account } = useWeb3React(); const { account } = useWeb3React();
const { keypair } = useVegaWallet(); const { keypair } = useVegaWallet();
@ -166,7 +167,7 @@ export const DepositForm = ({
{errors.asset.message} {errors.asset.message}
</InputError> </InputError>
)} )}
{FAUCETABLE && selectedAsset && ( {isFaucetable && selectedAsset && (
<UseButton onClick={requestFaucet}> <UseButton onClick={requestFaucet}>
{t(`Get ${selectedAsset.symbol}`)} {t(`Get ${selectedAsset.symbol}`)}
</UseButton> </UseButton>

View File

@ -34,6 +34,7 @@ interface DepositManagerProps {
bridgeAddress: string; bridgeAddress: string;
assets: Asset[]; assets: Asset[];
initialAssetId?: string; initialAssetId?: string;
isFaucetable?: boolean;
} }
export const DepositManager = ({ export const DepositManager = ({
@ -41,6 +42,7 @@ export const DepositManager = ({
bridgeAddress, bridgeAddress,
assets, assets,
initialAssetId, initialAssetId,
isFaucetable,
}: DepositManagerProps) => { }: DepositManagerProps) => {
const [assetId, setAssetId] = useState<string | undefined>(initialAssetId); const [assetId, setAssetId] = useState<string | undefined>(initialAssetId);
@ -54,7 +56,7 @@ export const DepositManager = ({
asset?.source.__typename === 'ERC20' asset?.source.__typename === 'ERC20'
? asset.source.contractAddress ? asset.source.contractAddress
: undefined, : undefined,
process.env['NX_VEGA_ENV'] !== 'MAINNET' isFaucetable
); );
const bridgeContract = useBridgeContract(); const bridgeContract = useBridgeContract();
@ -101,6 +103,7 @@ export const DepositManager = ({
requestFaucet={faucet.perform} requestFaucet={faucet.perform}
limits={limits} limits={limits}
allowance={allowance} allowance={allowance}
isFaucetable={isFaucetable}
/> />
<TransactionDialog {...approve.transaction} name="approve" /> <TransactionDialog {...approve.transaction} name="approve" />
<TransactionDialog {...faucet.transaction} name="faucet" /> <TransactionDialog {...faucet.transaction} name="faucet" />

View File

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

View File

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

View File

@ -2,14 +2,17 @@ import { useQuery } from '@apollo/client';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import { Interval } from '@vegaprotocol/types'; import { Interval } from '@vegaprotocol/types';
import { AsyncRenderer, Dialog, Intent } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, Dialog, Intent } from '@vegaprotocol/ui-toolkit';
import { useState } from 'react';
import { MARKET_LIST_QUERY } from '../markets-container/markets-data-provider'; import { MARKET_LIST_QUERY } from '../markets-container/markets-data-provider';
import type { MarketList } from '../markets-container/__generated__/MarketList'; import type { MarketList } from '../markets-container/__generated__/MarketList';
import { SelectMarketList } from './select-market-list'; import { SelectMarketList } from './select-market-list';
export const LandingDialog = () => { interface LandingDialogProps {
const [open, setOpen] = useState(true); open: boolean;
setOpen: (open: boolean) => void;
}
export const LandingDialog = ({ open, setOpen }: LandingDialogProps) => {
const setClose = () => setOpen(false); const setClose = () => setOpen(false);
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600; const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
@ -21,7 +24,6 @@ export const LandingDialog = () => {
return ( return (
<AsyncRenderer loading={loading} error={error} data={data}> <AsyncRenderer loading={loading} error={error} data={data}>
{
<Dialog <Dialog
title={t('Select a market to get started')} title={t('Select a market to get started')}
intent={Intent.Prompt} intent={Intent.Prompt}
@ -29,9 +31,8 @@ export const LandingDialog = () => {
onChange={setClose} onChange={setClose}
titleClassNames="font-bold font-sans text-3xl tracking-tight mb-0 pl-8" titleClassNames="font-bold font-sans text-3xl tracking-tight mb-0 pl-8"
> >
<SelectMarketList data={data} /> <SelectMarketList data={data} onSelect={setClose} />
</Dialog> </Dialog>
}
</AsyncRenderer> </AsyncRenderer>
); );
}; };

View File

@ -1,15 +1,41 @@
import { render, screen } from '@testing-library/react'; import { fireEvent, render, screen } from '@testing-library/react';
import type { MarketList } from '../__generated__/MarketList'; import type { ReactNode } from 'react';
import type { MarketList } from '../markets-container/__generated__/MarketList';
import { SelectMarketList } from './select-market-list'; import { SelectMarketList } from './select-market-list';
jest.mock(
'next/link',
() =>
({ children }: { children: ReactNode }) =>
children
);
describe('SelectMarketList', () => { describe('SelectMarketList', () => {
it('should render', () => { 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('AAPL.MF21')).toBeTruthy();
expect(screen.getByText('-3.14%')).toBeTruthy(); expect(screen.getByText('-3.14%')).toBeTruthy();
expect(screen.getByText('141.75')).toBeTruthy(); expect(screen.getByText('141.75')).toBeTruthy();
expect(screen.getByText('Or view full market list')).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 = { const mockData = {

View File

@ -10,11 +10,12 @@ import type { MarketList } from '../markets-container/__generated__/MarketList';
export interface SelectMarketListProps { export interface SelectMarketListProps {
data: MarketList | undefined; data: MarketList | undefined;
onSelect: (id: string) => void;
} }
type CandleClose = Required<string>; type CandleClose = Required<string>;
export const SelectMarketList = ({ data }: SelectMarketListProps) => { export const SelectMarketList = ({ data, onSelect }: SelectMarketListProps) => {
const thClassNames = (direction: 'left' | 'right') => 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`; `px-8 text-${direction} font-sans font-normal text-ui-small leading-9 mb-0 text-dark/80 dark:text-white/80`;
const tdClassNames = const tdClassNames =
@ -49,8 +50,15 @@ export const SelectMarketList = ({ data }: SelectMarketListProps) => {
<td className={`${boldUnderlineClassNames} relative`}> <td className={`${boldUnderlineClassNames} relative`}>
<Link <Link
href={`/markets/${id}?portfolio=orders&trade=orderbook&chart=candles`} href={`/markets/${id}?portfolio=orders&trade=orderbook&chart=candles`}
passHref={true}
>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a
onClick={() => onSelect(id)}
data-testid={`market-link-${id}`}
> >
{marketName} {marketName}
</a>
</Link> </Link>
</td> </td>
<td className={tdClassNames}> <td className={tdClassNames}>

View File

@ -3,7 +3,7 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // 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 // GraphQL query operation: MarketList
@ -15,14 +15,6 @@ export interface MarketList_markets_data_market {
* Market ID * Market ID
*/ */
id: string; id: string;
/**
* Current state of the market
*/
state: MarketState;
/**
* Current mode of execution of the market
*/
tradingMode: MarketTradingMode;
} }
export interface MarketList_markets_data { export interface MarketList_markets_data {
@ -31,14 +23,6 @@ export interface MarketList_markets_data {
* market id of the associated mark price * market id of the associated mark price
*/ */
market: MarketList_markets_data_market; 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) * the mark price (actually an unsigned int)
*/ */
@ -66,7 +50,7 @@ export interface MarketList_markets_tradableInstrument_instrument {
/** /**
* Metadata for this instrument * Metadata for this instrument
*/ */
metadata?: MarketList_markets_tradableInstrument_instrument_metadata; metadata: MarketList_markets_tradableInstrument_instrument_metadata;
} }
export interface MarketList_markets_tradableInstrument { export interface MarketList_markets_tradableInstrument {

View File

@ -2,3 +2,4 @@ export * from './market-list-table';
export * from './markets-container'; export * from './markets-container';
export * from './markets-data-provider'; export * from './markets-data-provider';
export * from './summary-cell'; export * from './summary-cell';
export * from './__generated__/MarketList';

Some files were not shown because too many files have changed in this diff Show More