Merge branch 'master' of github.com:vegaprotocol/frontend-monorepo
This commit is contained in:
commit
c6284a91ef
@ -96,7 +96,7 @@ To run tests locally using your own wallets you can add the following environmen
|
|||||||
In CI linting, formatting and also run. These checks can be seen in the [CI workflow file](.github/workflows//test.yml).
|
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
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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,27 +26,29 @@ function App() {
|
|||||||
const client = useMemo(() => createClient(DATA_SOURCES.dataNodeUrl), []);
|
const client = useMemo(() => createClient(DATA_SOURCES.dataNodeUrl), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={theme}>
|
<EnvironmentProvider>
|
||||||
<TendermintWebsocketProvider>
|
<ThemeContext.Provider value={theme}>
|
||||||
<ApolloProvider client={client}>
|
<TendermintWebsocketProvider>
|
||||||
<div
|
<ApolloProvider client={client}>
|
||||||
className={`${
|
<div
|
||||||
menuOpen && 'h-[100vh] overflow-hidden'
|
className={`${
|
||||||
} antialiased m-0 bg-white dark:bg-black text-black dark:text-white`}
|
menuOpen && 'h-[100vh] overflow-hidden'
|
||||||
>
|
} antialiased m-0 bg-white dark:bg-black text-black dark:text-white`}
|
||||||
<div className="min-h-[100vh] max-w-[1300px] grid grid-rows-[repeat(2,_auto)_1fr] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)] md:grid-cols-[300px_1fr] border-black dark:border-white lg:border-l-1 lg:border-r-1 mx-auto">
|
>
|
||||||
<Header
|
<div className="min-h-[100vh] max-w-[1300px] grid grid-rows-[repeat(2,_auto)_1fr] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)] md:grid-cols-[300px_1fr] border-black dark:border-white lg:border-l-1 lg:border-r-1 mx-auto">
|
||||||
toggleTheme={toggleTheme}
|
<Header
|
||||||
menuOpen={menuOpen}
|
toggleTheme={toggleTheme}
|
||||||
setMenuOpen={setMenuOpen}
|
menuOpen={menuOpen}
|
||||||
/>
|
setMenuOpen={setMenuOpen}
|
||||||
<Nav menuOpen={menuOpen} />
|
/>
|
||||||
<Main />
|
<Nav menuOpen={menuOpen} />
|
||||||
|
<Main />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ApolloProvider>
|
||||||
</ApolloProvider>
|
</TendermintWebsocketProvider>
|
||||||
</TendermintWebsocketProvider>
|
</ThemeContext.Provider>
|
||||||
</ThemeContext.Provider>
|
</EnvironmentProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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": [],
|
||||||
|
@ -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,51 +38,53 @@ function App() {
|
|||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={theme}>
|
<EnvironmentProvider>
|
||||||
<ApolloProvider client={client}>
|
<ThemeContext.Provider value={theme}>
|
||||||
<VegaWalletProvider>
|
<ApolloProvider client={client}>
|
||||||
<AppLoader>
|
<VegaWalletProvider>
|
||||||
<div className="h-full dark:bg-black dark:text-white-60 bg-white text-black-60 grid grid-rows-[min-content,1fr]">
|
<AppLoader>
|
||||||
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
|
<div className="h-full dark:bg-black dark:text-white-60 bg-white text-black-60 grid grid-rows-[min-content,1fr]">
|
||||||
<DrawerToggle
|
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
|
||||||
onToggle={onToggle}
|
<DrawerToggle
|
||||||
variant={DRAWER_TOGGLE_VARIANTS.OPEN}
|
onToggle={onToggle}
|
||||||
className="xs:py-32 xs:px-16"
|
variant={DRAWER_TOGGLE_VARIANTS.OPEN}
|
||||||
/>
|
className="xs:py-32 xs:px-16"
|
||||||
|
|
||||||
<div className="flex items-center gap-4 ml-auto mr-8">
|
|
||||||
<VegaWalletConnectButton
|
|
||||||
setConnectDialog={(open) =>
|
|
||||||
setVegaWallet((x) => ({ ...x, connect: open }))
|
|
||||||
}
|
|
||||||
setManageDialog={(open) =>
|
|
||||||
setVegaWallet((x) => ({ ...x, manage: open }))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
|
|
||||||
|
<div className="flex items-center gap-4 ml-auto mr-8">
|
||||||
|
<VegaWalletConnectButton
|
||||||
|
setConnectDialog={(open) =>
|
||||||
|
setVegaWallet((x) => ({ ...x, connect: open }))
|
||||||
|
}
|
||||||
|
setManageDialog={(open) =>
|
||||||
|
setVegaWallet((x) => ({ ...x, manage: open }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Main isMenuOpen={menuOpen} onToggle={onToggle} />
|
||||||
|
|
||||||
|
<VegaConnectDialog
|
||||||
|
connectors={Connectors}
|
||||||
|
dialogOpen={vegaWallet.connect}
|
||||||
|
setDialogOpen={(open) =>
|
||||||
|
setVegaWallet((x) => ({ ...x, connect: open }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<VegaManageDialog
|
||||||
|
dialogOpen={vegaWallet.manage}
|
||||||
|
setDialogOpen={(open) =>
|
||||||
|
setVegaWallet((x) => ({ ...x, manage: open }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</AppLoader>
|
||||||
<Main isMenuOpen={menuOpen} onToggle={onToggle} />
|
</VegaWalletProvider>
|
||||||
|
</ApolloProvider>
|
||||||
<VegaConnectDialog
|
</ThemeContext.Provider>
|
||||||
connectors={Connectors}
|
</EnvironmentProvider>
|
||||||
dialogOpen={vegaWallet.connect}
|
|
||||||
setDialogOpen={(open) =>
|
|
||||||
setVegaWallet((x) => ({ ...x, connect: open }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<VegaManageDialog
|
|
||||||
dialogOpen={vegaWallet.manage}
|
|
||||||
setDialogOpen={(open) =>
|
|
||||||
setVegaWallet((x) => ({ ...x, manage: open }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AppLoader>
|
|
||||||
</VegaWalletProvider>
|
|
||||||
</ApolloProvider>
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 })}
|
[transactionStatus, submit]
|
||||||
price={
|
);
|
||||||
market.depth.lastTrade
|
|
||||||
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (order.type === OrderType.Limit) {
|
|
||||||
ticket = (
|
|
||||||
<DealTicketLimitForm
|
|
||||||
price={order.price}
|
|
||||||
size={order.size}
|
|
||||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
|
||||||
onSizeChange={(size) => updateOrder({ size })}
|
|
||||||
onPriceChange={(price) => updateOrder({ price })}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid ticket type');
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = (e: FormEvent<HTMLFormElement>): Promise<void> => {
|
|
||||||
e.preventDefault();
|
|
||||||
return submit(order);
|
|
||||||
};
|
|
||||||
|
|
||||||
const 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,27 +102,49 @@ 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: (
|
||||||
<>
|
<>
|
||||||
<TimeInForceSelector
|
<Controller
|
||||||
order={order}
|
name="timeInForce"
|
||||||
onSelect={(timeInForce) => updateOrder({ timeInForce })}
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TimeInForceSelector
|
||||||
|
value={field.value}
|
||||||
|
orderType={orderType}
|
||||||
|
onSelect={field.onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
{order.timeInForce === OrderTimeInForce.GTT && (
|
{orderType === OrderType.Limit &&
|
||||||
<ExpirySelector
|
orderTimeInForce === OrderTimeInForce.GTT && (
|
||||||
order={order}
|
<Controller
|
||||||
onSelect={(date) => {
|
name="expiration"
|
||||||
if (date) {
|
control={control}
|
||||||
updateOrder({ expiration: date });
|
render={({ field }) => (
|
||||||
}
|
<ExpirySelector
|
||||||
}}
|
value={field.value}
|
||||||
/>
|
onSelect={field.onChange}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -136,11 +153,22 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
|||||||
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
|
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>
|
||||||
);
|
);
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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,19 +15,21 @@ function App() {
|
|||||||
const [theme, toggleTheme] = useThemeSwitcher();
|
const [theme, toggleTheme] = useThemeSwitcher();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={theme}>
|
<EnvironmentProvider>
|
||||||
<div className="w-screen min-h-screen grid pb-24 bg-white text-black-95 dark:bg-black dark:text-white-80">
|
<ThemeContext.Provider value={theme}>
|
||||||
<div className="layout-grid w-screen justify-self-center">
|
<div className="w-screen min-h-screen grid pb-24 bg-white text-black-95 dark:bg-black dark:text-white-80">
|
||||||
<Header toggleTheme={toggleTheme} />
|
<div className="layout-grid w-screen justify-self-center">
|
||||||
<StatsManager
|
<Header toggleTheme={toggleTheme} />
|
||||||
envName={envName}
|
<StatsManager
|
||||||
statsEndpoint={statsEndpoint}
|
envName={envName}
|
||||||
nodesEndpoint={nodesEndpoint}
|
statsEndpoint={statsEndpoint}
|
||||||
className="max-w-3xl px-24"
|
nodesEndpoint={nodesEndpoint}
|
||||||
/>
|
className="max-w-3xl px-24"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ThemeContext.Provider>
|
||||||
</ThemeContext.Provider>
|
</EnvironmentProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -20,37 +20,40 @@ 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>
|
||||||
<AppStateProvider>
|
<EnvironmentProvider>
|
||||||
<Web3Provider connectors={Connectors}>
|
<AppStateProvider>
|
||||||
<Web3Connector>
|
<Web3Provider connectors={Connectors}>
|
||||||
<VegaWalletProvider>
|
<Web3Connector>
|
||||||
<ContractsProvider>
|
<VegaWalletProvider>
|
||||||
<AppLoader>
|
<ContractsProvider>
|
||||||
<BalanceManager>
|
<AppLoader>
|
||||||
<>
|
<BalanceManager>
|
||||||
<div className="app dark max-w-[1300px] mx-auto my-0 grid grid-rows-[min-content_1fr_min-content] min-h-full lg:border-l-1 lg:border-r-1 lg:border-white font-sans text-body lg:text-body-large text-white-80">
|
<>
|
||||||
<AppBanner />
|
<div className="app dark max-w-[1300px] mx-auto my-0 grid grid-rows-[min-content_1fr_min-content] min-h-full lg:border-l-1 lg:border-r-1 lg:border-white font-sans text-body lg:text-body-large text-white-80">
|
||||||
<TemplateSidebar sidebar={sideBar}>
|
<AppBanner />
|
||||||
<AppRouter />
|
<TemplateSidebar sidebar={sideBar}>
|
||||||
</TemplateSidebar>
|
<AppRouter />
|
||||||
<AppFooter />
|
</TemplateSidebar>
|
||||||
</div>
|
<AppFooter />
|
||||||
<VegaWalletDialogs />
|
</div>
|
||||||
<TransactionModal />
|
<VegaWalletDialogs />
|
||||||
</>
|
<TransactionModal />
|
||||||
</BalanceManager>
|
</>
|
||||||
</AppLoader>
|
</BalanceManager>
|
||||||
</ContractsProvider>
|
</AppLoader>
|
||||||
</VegaWalletProvider>
|
</ContractsProvider>
|
||||||
</Web3Connector>
|
</VegaWalletProvider>
|
||||||
</Web3Provider>
|
</Web3Connector>
|
||||||
</AppStateProvider>
|
</Web3Provider>
|
||||||
|
</AppStateProvider>
|
||||||
|
</EnvironmentProvider>
|
||||||
</Router>
|
</Router>
|
||||||
</GraphQlProvider>
|
</GraphQlProvider>
|
||||||
);
|
);
|
||||||
|
@ -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 (
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import type { EthereumChainId } from '@vegaprotocol/smart-contracts';
|
|
||||||
import {
|
|
||||||
EnvironmentConfig,
|
|
||||||
EthereumChainIds,
|
|
||||||
} from '@vegaprotocol/smart-contracts';
|
|
||||||
|
|
||||||
import type { Networks } from './vega';
|
|
||||||
|
|
||||||
type VegaContracts = typeof EnvironmentConfig[Networks];
|
|
||||||
|
|
||||||
const appChainId = Number(process.env['NX_ETHEREUM_CHAIN_ID'] || 3);
|
|
||||||
|
|
||||||
export const APP_ENV = process.env['NX_VEGA_ENV'] as Networks;
|
|
||||||
|
|
||||||
const Addresses: Record<number, VegaContracts> = {
|
|
||||||
1: EnvironmentConfig.MAINNET,
|
|
||||||
3: EnvironmentConfig[APP_ENV],
|
|
||||||
};
|
|
||||||
|
|
||||||
export type { EthereumChainId };
|
|
||||||
export { EthereumChainIds };
|
|
||||||
|
|
||||||
/** Contract addresses for the different contracts in the VEGA ecosystem */
|
|
||||||
export const ADDRESSES = Addresses[appChainId];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Chain ID the environment is configured for.
|
|
||||||
* Normally this is 0x3 (Ropsten) for dev and 0x1 (Mainnet) for prod
|
|
||||||
*/
|
|
||||||
export const APP_CHAIN_ID = appChainId;
|
|
@ -1,5 +1,4 @@
|
|||||||
export * from './flags';
|
export * from './flags';
|
||||||
export * from './ethereum';
|
|
||||||
export * from './links';
|
export * from './links';
|
||||||
export * from './network-params';
|
export * from './network-params';
|
||||||
export * from './vega';
|
export * from './vega';
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
|
@ -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()}
|
||||||
|
@ -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 },
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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')}
|
{t('Connected Ethereum address')}
|
||||||
<EtherscanLink address={account} text={account} />
|
<Link
|
||||||
|
title={t('View address on Etherscan')}
|
||||||
|
href={`${ETHERSCAN_URL}/tx/${account}`}
|
||||||
|
>
|
||||||
|
{account}
|
||||||
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
<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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -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 &&
|
||||||
|
@ -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>
|
||||||
|
@ -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();
|
||||||
|
@ -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>
|
||||||
) : (
|
) : (
|
||||||
'-'
|
'-'
|
||||||
)}
|
)}
|
||||||
|
@ -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": [],
|
||||||
|
@ -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: {
|
||||||
|
77
apps/trading-e2e/src/support/mocks/generate-market-list.ts
Normal file
77
apps/trading-e2e/src/support/mocks/generate-market-list.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import merge from 'lodash/merge';
|
||||||
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
import type { MarketList, MarketList_markets } from '@vegaprotocol/market-list';
|
||||||
|
|
||||||
|
export const generateMarketList = (
|
||||||
|
override?: PartialDeep<MarketList>
|
||||||
|
): MarketList => {
|
||||||
|
const markets: MarketList_markets[] = [
|
||||||
|
{
|
||||||
|
id: 'market-id',
|
||||||
|
decimalPlaces: 5,
|
||||||
|
data: {
|
||||||
|
market: {
|
||||||
|
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||||
|
__typename: 'Market',
|
||||||
|
},
|
||||||
|
markPrice: '4612690058',
|
||||||
|
__typename: 'MarketData',
|
||||||
|
},
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'BTC/USD Monthly',
|
||||||
|
code: 'BTCUSD.MF21',
|
||||||
|
metadata: {
|
||||||
|
__typename: 'InstrumentMetadata',
|
||||||
|
tags: ['tag1'],
|
||||||
|
},
|
||||||
|
__typename: 'Instrument',
|
||||||
|
},
|
||||||
|
__typename: 'TradableInstrument',
|
||||||
|
},
|
||||||
|
marketTimestamps: {
|
||||||
|
__typename: 'MarketTimestamps',
|
||||||
|
open: '',
|
||||||
|
close: '',
|
||||||
|
},
|
||||||
|
candles: [{ __typename: 'Candle', open: '100', close: '100' }],
|
||||||
|
__typename: 'Market',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'test-market-suspended',
|
||||||
|
decimalPlaces: 2,
|
||||||
|
data: {
|
||||||
|
market: {
|
||||||
|
id: '34d95e10faa00c21d19d382d6d7e6fc9722a96985369f0caec041b0f44b775ed',
|
||||||
|
__typename: 'Market',
|
||||||
|
},
|
||||||
|
markPrice: '8441',
|
||||||
|
__typename: 'MarketData',
|
||||||
|
},
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'SOL/USD',
|
||||||
|
code: 'SOLUSD',
|
||||||
|
metadata: {
|
||||||
|
__typename: 'InstrumentMetadata',
|
||||||
|
tags: ['tag1'],
|
||||||
|
},
|
||||||
|
__typename: 'Instrument',
|
||||||
|
},
|
||||||
|
__typename: 'TradableInstrument',
|
||||||
|
},
|
||||||
|
marketTimestamps: {
|
||||||
|
__typename: 'MarketTimestamps',
|
||||||
|
open: '',
|
||||||
|
close: '',
|
||||||
|
},
|
||||||
|
candles: [{ __typename: 'Candle', open: '100', close: '100' }],
|
||||||
|
__typename: 'Market',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const defaultResult = {
|
||||||
|
markets,
|
||||||
|
};
|
||||||
|
|
||||||
|
return merge(defaultResult, override);
|
||||||
|
};
|
@ -31,7 +31,7 @@ export default class WithdrawalsPage extends BasePage {
|
|||||||
validateConnectWalletText() {
|
validateConnectWalletText() {
|
||||||
cy.getByTestId(this.connectVegaWalletText).should(
|
cy.getByTestId(this.connectVegaWalletText).should(
|
||||||
'have.text',
|
'have.text',
|
||||||
'Please connect your Vega wallet'
|
'Connect your Vega wallet'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
1
apps/trading/components/vega-wallet-container/index.ts
Normal file
1
apps/trading/components/vega-wallet-container/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './vega-wallet-container';
|
@ -0,0 +1,27 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { VegaWalletContainer } from './vega-wallet-container';
|
||||||
|
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||||
|
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
|
||||||
|
const generateJsx = (context: PartialDeep<VegaWalletContextShape>) => {
|
||||||
|
return (
|
||||||
|
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
|
||||||
|
<VegaWalletContainer>
|
||||||
|
<div data-testid="child" />
|
||||||
|
</VegaWalletContainer>
|
||||||
|
</VegaWalletContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('VegaWalletContainer', () => {
|
||||||
|
it('doesnt render children if not connected', () => {
|
||||||
|
render(generateJsx({ keypair: null }));
|
||||||
|
expect(screen.queryByTestId('child')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders children if connected', () => {
|
||||||
|
render(generateJsx({ keypair: { pub: '0x123' } }));
|
||||||
|
expect(screen.getByTestId('child')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,34 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import { useGlobalStore } from '../../stores';
|
||||||
|
|
||||||
|
interface VegaWalletContainerProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
|
||||||
|
const store = useGlobalStore();
|
||||||
|
const { keypair } = useVegaWallet();
|
||||||
|
|
||||||
|
if (!keypair) {
|
||||||
|
return (
|
||||||
|
<Splash>
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="mb-12" data-testid="connect-vega-wallet-text">
|
||||||
|
{t('Connect your Vega wallet')}
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={() => store.setVegaWalletConnectDialog(true)}
|
||||||
|
data-testid="vega-wallet-connect"
|
||||||
|
>
|
||||||
|
{t('Connect')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Splash>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
@ -108,11 +108,7 @@ export const Web3Content = ({
|
|||||||
const { isActive, error, connector, chainId } = useWeb3React();
|
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]);
|
||||||
|
@ -7,81 +7,85 @@ 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 (
|
||||||
<ThemeContext.Provider value={theme}>
|
<EnvironmentProvider>
|
||||||
<ApolloProvider client={client}>
|
<ThemeContext.Provider value={theme}>
|
||||||
<VegaWalletProvider>
|
<ApolloProvider client={client}>
|
||||||
<AppLoader>
|
<VegaWalletProvider>
|
||||||
<Head>
|
<AppLoader>
|
||||||
<link
|
<Head>
|
||||||
rel="preload"
|
<link
|
||||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
rel="preload"
|
||||||
as="font"
|
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||||
type="font/woff2"
|
as="font"
|
||||||
crossOrigin="anonymous"
|
type="font/woff2"
|
||||||
/>
|
crossOrigin="anonymous"
|
||||||
<title>{t('Welcome to Vega trading!')}</title>
|
/>
|
||||||
<link
|
<title>{t('Welcome to Vega trading!')}</title>
|
||||||
rel="icon"
|
<link
|
||||||
type="image/x-icon"
|
rel="icon"
|
||||||
href="https://static.vega.xyz/favicon.ico"
|
type="image/x-icon"
|
||||||
/>
|
href="https://static.vega.xyz/favicon.ico"
|
||||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
/>
|
||||||
</Head>
|
<link
|
||||||
<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]">
|
rel="stylesheet"
|
||||||
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
|
href="https://static.vega.xyz/fonts.css"
|
||||||
<Navbar />
|
/>
|
||||||
<div className="flex items-center gap-4 ml-auto mr-8">
|
</Head>
|
||||||
<VegaWalletConnectButton
|
<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]">
|
||||||
setConnectDialog={(open) =>
|
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
|
||||||
setVegaWallet((x) => ({ ...x, connect: open }))
|
<Navbar />
|
||||||
}
|
<div className="flex items-center gap-4 ml-auto mr-8">
|
||||||
setManageDialog={(open) =>
|
<VegaWalletConnectButton
|
||||||
setVegaWallet((x) => ({ ...x, manage: open }))
|
setConnectDialog={(open) => {
|
||||||
}
|
store.setVegaWalletConnectDialog(open);
|
||||||
/>
|
}}
|
||||||
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
|
setManageDialog={(open) => {
|
||||||
|
store.setVegaWalletManageDialog(open);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<main data-testid={pageProps.page}>
|
||||||
|
{/* @ts-ignore conflict between @types/react and nextjs internal types */}
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</main>
|
||||||
|
<VegaConnectDialog
|
||||||
|
connectors={Connectors}
|
||||||
|
dialogOpen={store.vegaWalletConnectDialog}
|
||||||
|
setDialogOpen={(open) =>
|
||||||
|
store.setVegaWalletConnectDialog(open)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<VegaManageDialog
|
||||||
|
dialogOpen={store.vegaWalletManageDialog}
|
||||||
|
setDialogOpen={(open) =>
|
||||||
|
store.setVegaWalletManageDialog(open)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<main data-testid={pageProps.page}>
|
</AppLoader>
|
||||||
{/* @ts-ignore conflict between @types/react and nextjs internal types */}
|
</VegaWalletProvider>
|
||||||
<Component {...pageProps} />
|
</ApolloProvider>
|
||||||
</main>
|
</ThemeContext.Provider>
|
||||||
<VegaConnectDialog
|
</EnvironmentProvider>
|
||||||
connectors={Connectors}
|
|
||||||
dialogOpen={vegaWallet.connect}
|
|
||||||
setDialogOpen={(open) =>
|
|
||||||
setVegaWallet((x) => ({ ...x, connect: open }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<VegaManageDialog
|
|
||||||
dialogOpen={vegaWallet.manage}
|
|
||||||
setDialogOpen={(open) =>
|
|
||||||
setVegaWallet((x) => ({ ...x, manage: open }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AppLoader>
|
|
||||||
</VegaWalletProvider>
|
|
||||||
</ApolloProvider>
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
const marketId = marketList(data)[0]?.id;
|
|
||||||
window.history.replaceState(
|
useEffect(() => {
|
||||||
data,
|
if (data) {
|
||||||
'',
|
const marketId = marketList(data)[0]?.id;
|
||||||
marketId ? `/markets/${marketId}` : '/markets'
|
|
||||||
);
|
// If a default market is found, go to it with the landing dialog open
|
||||||
}
|
if (marketId) {
|
||||||
|
setLandingDialog(true);
|
||||||
|
replace(`/markets/${marketId}`);
|
||||||
|
}
|
||||||
|
// Fallback to the markets list page
|
||||||
|
else {
|
||||||
|
replace('/markets');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [data, replace, setLandingDialog]);
|
||||||
|
|
||||||
return (
|
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>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 (
|
||||||
<TradeGrid market={market} />
|
<>
|
||||||
) : (
|
{w > 960 ? (
|
||||||
<TradePanels market={market} />
|
<TradeGrid market={market} />
|
||||||
|
) : (
|
||||||
|
<TradePanels market={market} />
|
||||||
|
)}
|
||||||
|
<LandingDialog
|
||||||
|
open={store.landingDialog}
|
||||||
|
setOpen={(isOpen) => store.setLandingDialog(isOpen)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -3,7 +3,7 @@ import { gql } from '@apollo/client';
|
|||||||
import { PageQueryContainer } from '../../../components/page-query-container';
|
import { 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'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -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,14 +22,16 @@ const Withdraw = () => {
|
|||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Web3Container
|
<VegaWalletContainer>
|
||||||
render={() => (
|
<Web3Container
|
||||||
<div className="max-w-[420px] p-24 mx-auto">
|
render={() => (
|
||||||
<h1 className="text-h3 mb-12">{t('Withdraw')}</h1>
|
<div className="max-w-[420px] p-24 mx-auto">
|
||||||
<WithdrawPageContainer assetId={assetId} />
|
<h1 className="text-h3 mb-12">{t('Withdraw')}</h1>
|
||||||
</div>
|
<WithdrawPageContainer assetId={assetId} />
|
||||||
)}
|
</div>
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
</VegaWalletContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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}
|
||||||
|
@ -1,30 +1,26 @@
|
|||||||
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 (
|
||||||
<Web3Container
|
<VegaWalletContainer>
|
||||||
render={() => (
|
<Web3Container
|
||||||
<div className="h-full grid grid grid-rows-[min-content,1fr]">
|
render={() => (
|
||||||
<header className="flex justify-between p-24">
|
<div className="h-full grid grid grid-rows-[min-content,1fr]">
|
||||||
<h1 className="text-h3">{t('Withdrawals')}</h1>
|
<header className="flex justify-between p-24">
|
||||||
<AnchorButton href="/portfolio/withdraw">
|
<h1 className="text-h3">{t('Withdrawals')}</h1>
|
||||||
{t('Start withdrawal')}
|
<AnchorButton href="/portfolio/withdraw">
|
||||||
</AnchorButton>
|
{t('Start withdrawal')}
|
||||||
</header>
|
</AnchorButton>
|
||||||
<WithdrawalsPageContainer />
|
</header>
|
||||||
</div>
|
<WithdrawalsPageContainer />
|
||||||
)}
|
</div>
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
</VegaWalletContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
26
apps/trading/stores/global.ts
Normal file
26
apps/trading/stores/global.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import type { SetState } from 'zustand';
|
||||||
|
import create from 'zustand';
|
||||||
|
|
||||||
|
interface GlobalStore {
|
||||||
|
vegaWalletConnectDialog: boolean;
|
||||||
|
setVegaWalletConnectDialog: (isOpen: boolean) => void;
|
||||||
|
vegaWalletManageDialog: boolean;
|
||||||
|
setVegaWalletManageDialog: (isOpen: boolean) => void;
|
||||||
|
landingDialog: boolean;
|
||||||
|
setLandingDialog: (isOpen: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGlobalStore = create((set: SetState<GlobalStore>) => ({
|
||||||
|
vegaWalletConnectDialog: false,
|
||||||
|
setVegaWalletConnectDialog: (isOpen: boolean) => {
|
||||||
|
set({ vegaWalletConnectDialog: isOpen });
|
||||||
|
},
|
||||||
|
vegaWalletManageDialog: false,
|
||||||
|
setVegaWalletManageDialog: (isOpen: boolean) => {
|
||||||
|
set({ vegaWalletManageDialog: isOpen });
|
||||||
|
},
|
||||||
|
landingDialog: false,
|
||||||
|
setLandingDialog: (isOpen: boolean) => {
|
||||||
|
set({ landingDialog: isOpen });
|
||||||
|
},
|
||||||
|
}));
|
1
apps/trading/stores/index.ts
Normal file
1
apps/trading/stores/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './global';
|
@ -9,7 +9,6 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"incremental": true
|
"incremental": true
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
33
libs/deal-ticket/src/components/deal-ticket-amount.tsx
Normal file
33
libs/deal-ticket/src/components/deal-ticket-amount.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import type { UseFormRegister } from 'react-hook-form';
|
||||||
|
import { OrderType } from '@vegaprotocol/wallet';
|
||||||
|
import type { Order } from '../utils/get-default-order';
|
||||||
|
import { DealTicketMarketAmount } from './deal-ticket-market-amount';
|
||||||
|
import { DealTicketLimitAmount } from './deal-ticket-limit-amount';
|
||||||
|
|
||||||
|
export interface DealTicketAmountProps {
|
||||||
|
orderType: OrderType;
|
||||||
|
step: number;
|
||||||
|
register: UseFormRegister<Order>;
|
||||||
|
quoteName: string;
|
||||||
|
price?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAmountComponent = (type: OrderType) => {
|
||||||
|
switch (type) {
|
||||||
|
case OrderType.Market:
|
||||||
|
return DealTicketMarketAmount;
|
||||||
|
case OrderType.Limit:
|
||||||
|
return DealTicketLimitAmount;
|
||||||
|
default: {
|
||||||
|
throw new Error('Invalid ticket type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DealTicketAmount = ({
|
||||||
|
orderType,
|
||||||
|
...props
|
||||||
|
}: DealTicketAmountProps) => {
|
||||||
|
const AmountComponent = getAmountComponent(orderType);
|
||||||
|
return <AmountComponent {...props} />;
|
||||||
|
};
|
@ -4,7 +4,7 @@ import { DealTicketManager } from './deal-ticket-manager';
|
|||||||
import type {
|
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 {
|
@ -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>
|
@ -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;
|
@ -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>
|
@ -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();
|
|
||||||
|
|
||||||
fireEvent.change(screen.getByTestId('order-size'), {
|
await act(async () => {
|
||||||
target: { value: '200' },
|
fireEvent.change(screen.getByTestId('order-size'), {
|
||||||
|
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'), {
|
126
libs/deal-ticket/src/components/deal-ticket.tsx
Normal file
126
libs/deal-ticket/src/components/deal-ticket.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
|
import { OrderType, OrderTimeInForce } from '@vegaprotocol/wallet';
|
||||||
|
import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { TypeSelector } from './type-selector';
|
||||||
|
import { SideSelector } from './side-selector';
|
||||||
|
import { DealTicketAmount } from './deal-ticket-amount';
|
||||||
|
import { TimeInForceSelector } from './time-in-force-selector';
|
||||||
|
import { ExpirySelector } from './expiry-selector';
|
||||||
|
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||||
|
import type { Order } from '../utils/get-default-order';
|
||||||
|
import { getDefaultOrder } from '../utils/get-default-order';
|
||||||
|
import { useOrderValidation } from '../hooks/use-order-validation';
|
||||||
|
|
||||||
|
export type TransactionStatus = 'default' | 'pending';
|
||||||
|
|
||||||
|
export interface DealTicketProps {
|
||||||
|
market: DealTicketQuery_market;
|
||||||
|
submit: (order: Order) => void;
|
||||||
|
transactionStatus: TransactionStatus;
|
||||||
|
defaultOrder?: Order;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DealTicket = ({
|
||||||
|
market,
|
||||||
|
submit,
|
||||||
|
transactionStatus,
|
||||||
|
}: DealTicketProps) => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Order>({
|
||||||
|
mode: 'onChange',
|
||||||
|
defaultValues: getDefaultOrder(market),
|
||||||
|
});
|
||||||
|
|
||||||
|
const step = toDecimal(market.positionDecimalPlaces);
|
||||||
|
const orderType = watch('type');
|
||||||
|
const orderTimeInForce = watch('timeInForce');
|
||||||
|
const invalidText = useOrderValidation({
|
||||||
|
step,
|
||||||
|
market,
|
||||||
|
orderType,
|
||||||
|
orderTimeInForce,
|
||||||
|
fieldErrors: errors,
|
||||||
|
});
|
||||||
|
const isDisabled = transactionStatus === 'pending' || Boolean(invalidText);
|
||||||
|
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
(order: Order) => {
|
||||||
|
if (!isDisabled && !invalidText) {
|
||||||
|
submit(order);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isDisabled, invalidText, submit]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="px-4 py-8" noValidate>
|
||||||
|
<Controller
|
||||||
|
name="type"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TypeSelector value={field.value} onSelect={field.onChange} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="side"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<SideSelector value={field.value} onSelect={field.onChange} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<DealTicketAmount
|
||||||
|
orderType={orderType}
|
||||||
|
step={0.02}
|
||||||
|
register={register}
|
||||||
|
price={
|
||||||
|
market.depth.lastTrade
|
||||||
|
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="timeInForce"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TimeInForceSelector
|
||||||
|
value={field.value}
|
||||||
|
orderType={orderType}
|
||||||
|
onSelect={field.onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{orderType === OrderType.Limit &&
|
||||||
|
orderTimeInForce === OrderTimeInForce.GTT && (
|
||||||
|
<Controller
|
||||||
|
name="expiration"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<ExpirySelector value={field.value} onSelect={field.onChange} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
className="w-full mb-8"
|
||||||
|
variant="primary"
|
||||||
|
type="submit"
|
||||||
|
disabled={isDisabled}
|
||||||
|
data-testid="place-order"
|
||||||
|
>
|
||||||
|
{transactionStatus === 'pending' ? t('Pending...') : t('Place order')}
|
||||||
|
</Button>
|
||||||
|
{invalidText && (
|
||||||
|
<InputError className="mb-8" data-testid="dealticket-error-message">
|
||||||
|
{invalidText}
|
||||||
|
</InputError>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
@ -1,14 +1,13 @@
|
|||||||
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
|
import { 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 (
|
@ -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';
|
@ -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>
|
@ -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"
|
@ -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>
|
@ -1,57 +0,0 @@
|
|||||||
import { OrderTimeInForce } from '@vegaprotocol/wallet';
|
|
||||||
import type { TransactionStatus } from './deal-ticket';
|
|
||||||
import { ExpirySelector } from './expiry-selector';
|
|
||||||
import { SideSelector } from './side-selector';
|
|
||||||
import { SubmitButton } from './submit-button';
|
|
||||||
import { DealTicketLimitForm } from './deal-ticket-limit-form';
|
|
||||||
import { TimeInForceSelector } from './time-in-force-selector';
|
|
||||||
import { TypeSelector } from './type-selector';
|
|
||||||
import type { Order } from './use-order-state';
|
|
||||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
|
||||||
|
|
||||||
interface DealTicketLimitProps {
|
|
||||||
order: Order;
|
|
||||||
updateOrder: (order: Partial<Order>) => void;
|
|
||||||
transactionStatus: TransactionStatus;
|
|
||||||
market: DealTicketQuery_market;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DealTicketLimit = ({
|
|
||||||
order,
|
|
||||||
updateOrder,
|
|
||||||
transactionStatus,
|
|
||||||
market,
|
|
||||||
}: DealTicketLimitProps) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TypeSelector order={order} onSelect={(type) => updateOrder({ type })} />
|
|
||||||
<SideSelector order={order} onSelect={(side) => updateOrder({ side })} />
|
|
||||||
<DealTicketLimitForm
|
|
||||||
price={order.price}
|
|
||||||
size={order.size}
|
|
||||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
|
||||||
onSizeChange={(size) => updateOrder({ size })}
|
|
||||||
onPriceChange={(price) => updateOrder({ price })}
|
|
||||||
/>
|
|
||||||
<TimeInForceSelector
|
|
||||||
order={order}
|
|
||||||
onSelect={(timeInForce) => updateOrder({ timeInForce })}
|
|
||||||
/>
|
|
||||||
{order.timeInForce === OrderTimeInForce.GTT && (
|
|
||||||
<ExpirySelector
|
|
||||||
order={order}
|
|
||||||
onSelect={(date) => {
|
|
||||||
if (date) {
|
|
||||||
updateOrder({ expiration: date });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<SubmitButton
|
|
||||||
transactionStatus={transactionStatus}
|
|
||||||
market={market}
|
|
||||||
order={order}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,49 +0,0 @@
|
|||||||
import type { TransactionStatus } from './deal-ticket';
|
|
||||||
import { SideSelector } from './side-selector';
|
|
||||||
import { DealTicketMarketForm } from './deal-ticket-market-form';
|
|
||||||
import { SubmitButton } from './submit-button';
|
|
||||||
import { TimeInForceSelector } from './time-in-force-selector';
|
|
||||||
import { TypeSelector } from './type-selector';
|
|
||||||
import type { Order } from './use-order-state';
|
|
||||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
|
||||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
|
||||||
|
|
||||||
interface DealTicketMarketProps {
|
|
||||||
order: Order;
|
|
||||||
updateOrder: (order: Partial<Order>) => void;
|
|
||||||
transactionStatus: TransactionStatus;
|
|
||||||
market: DealTicketQuery_market;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DealTicketMarket = ({
|
|
||||||
order,
|
|
||||||
updateOrder,
|
|
||||||
transactionStatus,
|
|
||||||
market,
|
|
||||||
}: DealTicketMarketProps) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TypeSelector order={order} onSelect={(type) => updateOrder({ type })} />
|
|
||||||
<SideSelector order={order} onSelect={(side) => updateOrder({ side })} />
|
|
||||||
<DealTicketMarketForm
|
|
||||||
size={order.size}
|
|
||||||
onSizeChange={(size) => updateOrder({ size })}
|
|
||||||
price={
|
|
||||||
market.depth.lastTrade
|
|
||||||
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
|
||||||
/>
|
|
||||||
<TimeInForceSelector
|
|
||||||
order={order}
|
|
||||||
onSelect={(timeInForce) => updateOrder({ timeInForce })}
|
|
||||||
/>
|
|
||||||
<SubmitButton
|
|
||||||
transactionStatus={transactionStatus}
|
|
||||||
market={market}
|
|
||||||
order={order}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,67 +0,0 @@
|
|||||||
import type { FormEvent } from 'react';
|
|
||||||
import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
|
||||||
import type { Order } from './use-order-state';
|
|
||||||
import { useOrderState } from './use-order-state';
|
|
||||||
import { DealTicketMarket } from './deal-ticket-market';
|
|
||||||
import { DealTicketLimit } from './deal-ticket-limit';
|
|
||||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
|
||||||
|
|
||||||
const DEFAULT_ORDER: Order = {
|
|
||||||
type: OrderType.Market,
|
|
||||||
side: OrderSide.Buy,
|
|
||||||
size: '1',
|
|
||||||
timeInForce: OrderTimeInForce.IOC,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TransactionStatus = 'default' | 'pending';
|
|
||||||
|
|
||||||
export interface DealTicketProps {
|
|
||||||
market: DealTicketQuery_market;
|
|
||||||
submit: (order: Order) => void;
|
|
||||||
transactionStatus: TransactionStatus;
|
|
||||||
defaultOrder?: Order;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DealTicket = ({
|
|
||||||
market,
|
|
||||||
submit,
|
|
||||||
transactionStatus,
|
|
||||||
defaultOrder = DEFAULT_ORDER,
|
|
||||||
}: DealTicketProps) => {
|
|
||||||
const [order, updateOrder] = useOrderState(defaultOrder);
|
|
||||||
|
|
||||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
submit(order);
|
|
||||||
};
|
|
||||||
|
|
||||||
let ticket = null;
|
|
||||||
|
|
||||||
if (order.type === OrderType.Market) {
|
|
||||||
ticket = (
|
|
||||||
<DealTicketMarket
|
|
||||||
order={order}
|
|
||||||
updateOrder={updateOrder}
|
|
||||||
transactionStatus={transactionStatus}
|
|
||||||
market={market}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (order.type === OrderType.Limit) {
|
|
||||||
ticket = (
|
|
||||||
<DealTicketLimit
|
|
||||||
order={order}
|
|
||||||
updateOrder={updateOrder}
|
|
||||||
transactionStatus={transactionStatus}
|
|
||||||
market={market}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid ticket type');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit} className="px-4 py-8">
|
|
||||||
{ticket}
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,14 +1,42 @@
|
|||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { 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
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -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,
|
196
libs/deal-ticket/src/hooks/use-order-validation.spec.tsx
Normal file
196
libs/deal-ticket/src/hooks/use-order-validation.spec.tsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
import {
|
||||||
|
OrderTimeInForce,
|
||||||
|
OrderType,
|
||||||
|
useVegaWallet,
|
||||||
|
} from '@vegaprotocol/wallet';
|
||||||
|
import type {
|
||||||
|
VegaWalletContextShape,
|
||||||
|
VegaKeyExtended,
|
||||||
|
} from '@vegaprotocol/wallet';
|
||||||
|
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||||
|
import type { ValidationProps } from './use-order-validation';
|
||||||
|
import { useOrderValidation } from './use-order-validation';
|
||||||
|
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||||
|
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/wallet');
|
||||||
|
|
||||||
|
const market: DealTicketQuery_market = {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: 'market-id',
|
||||||
|
decimalPlaces: 2,
|
||||||
|
positionDecimalPlaces: 1,
|
||||||
|
tradingMode: MarketTradingMode.Continuous,
|
||||||
|
state: MarketState.Active,
|
||||||
|
tradableInstrument: {
|
||||||
|
__typename: 'TradableInstrument',
|
||||||
|
instrument: {
|
||||||
|
__typename: 'Instrument',
|
||||||
|
product: {
|
||||||
|
__typename: 'Future',
|
||||||
|
quoteName: 'quote-name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
depth: {
|
||||||
|
__typename: 'MarketDepth',
|
||||||
|
lastTrade: {
|
||||||
|
__typename: 'Trade',
|
||||||
|
price: '100',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultWalletContext = {
|
||||||
|
keypair: {
|
||||||
|
name: 'keypair0',
|
||||||
|
tainted: false,
|
||||||
|
pub: '111111__111111',
|
||||||
|
} as VegaKeyExtended,
|
||||||
|
keypairs: [],
|
||||||
|
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
|
||||||
|
connect: jest.fn(),
|
||||||
|
disconnect: jest.fn(),
|
||||||
|
selectPublicKey: jest.fn(),
|
||||||
|
connector: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultOrder = {
|
||||||
|
market,
|
||||||
|
step: 0.1,
|
||||||
|
orderType: OrderType.Market,
|
||||||
|
orderTimeInForce: OrderTimeInForce.FOK,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ERROR = {
|
||||||
|
KEY_MISSING: 'No public key selected',
|
||||||
|
KEY_TAINTED: 'Selected public key has been tainted',
|
||||||
|
MARKET_SUSPENDED: 'Market is currently suspended',
|
||||||
|
MARKET_INACTIVE: 'Market is no longer active',
|
||||||
|
MARKET_WAITING: 'Market is not active yet',
|
||||||
|
MARKET_CONTINUOUS_LIMIT:
|
||||||
|
'Only limit orders are permitted when market is in auction',
|
||||||
|
MARKET_COUNTINUOUS_TIF:
|
||||||
|
'Only GTT, GTC and GFA are permitted when market is in auction',
|
||||||
|
FIELD_SIZE_REQ: 'An amount needs to be provided',
|
||||||
|
FIELD_SIZE_MIN: `The amount cannot be lower than "${defaultOrder.step}"`,
|
||||||
|
FIELD_PRICE_REQ: 'A price needs to be provided',
|
||||||
|
FIELD_PRICE_MIN: 'The price cannot be negative',
|
||||||
|
FIELD_PRICE_STEP_NULL: 'No decimal amounts allowed for this order',
|
||||||
|
FIELD_PRICE_STEP_DECIMAL: `The amount field only takes up to ${market.positionDecimalPlaces} decimals`,
|
||||||
|
};
|
||||||
|
|
||||||
|
function setup(
|
||||||
|
props?: Partial<ValidationProps>,
|
||||||
|
context?: Partial<VegaWalletContextShape>
|
||||||
|
) {
|
||||||
|
const mockUseVegaWallet = useVegaWallet as jest.Mock;
|
||||||
|
mockUseVegaWallet.mockReturnValue({ ...defaultWalletContext, context });
|
||||||
|
return renderHook(() => useOrderValidation({ ...defaultOrder, ...props }));
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Returns empty string when given valid data', () => {
|
||||||
|
const { result } = setup();
|
||||||
|
expect(result.current).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Returns an error message when no keypair found', async () => {
|
||||||
|
const { result } = setup(defaultOrder, { keypair: null });
|
||||||
|
expect(result.current).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Returns an error message when the keypair is tainted', async () => {
|
||||||
|
const { result } = setup(defaultOrder, {
|
||||||
|
keypair: { ...defaultWalletContext.keypair, tainted: true },
|
||||||
|
});
|
||||||
|
expect(result.current).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each`
|
||||||
|
state | errorMessage
|
||||||
|
${MarketState.Cancelled} | ${ERROR.MARKET_INACTIVE}
|
||||||
|
${MarketState.Closed} | ${ERROR.MARKET_INACTIVE}
|
||||||
|
${MarketState.Rejected} | ${ERROR.MARKET_INACTIVE}
|
||||||
|
${MarketState.Settled} | ${ERROR.MARKET_INACTIVE}
|
||||||
|
${MarketState.TradingTerminated} | ${ERROR.MARKET_INACTIVE}
|
||||||
|
${MarketState.Suspended} | ${ERROR.MARKET_SUSPENDED}
|
||||||
|
${MarketState.Pending} | ${ERROR.MARKET_WAITING}
|
||||||
|
${MarketState.Proposed} | ${ERROR.MARKET_WAITING}
|
||||||
|
`(
|
||||||
|
'Returns an error message for "$marketState" market',
|
||||||
|
async ({ state, errorMessage }) => {
|
||||||
|
const { result } = setup({ market: { ...defaultOrder.market, state } });
|
||||||
|
expect(result.current).toEqual(errorMessage);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each`
|
||||||
|
tradingMode | errorMessage
|
||||||
|
${MarketTradingMode.BatchAuction} | ${ERROR.MARKET_CONTINUOUS_LIMIT}
|
||||||
|
${MarketTradingMode.MonitoringAuction} | ${ERROR.MARKET_CONTINUOUS_LIMIT}
|
||||||
|
${MarketTradingMode.OpeningAuction} | ${ERROR.MARKET_CONTINUOUS_LIMIT}
|
||||||
|
`(
|
||||||
|
'Returns an error message when trying to submit a non-limit order for a "$tradingMode" market',
|
||||||
|
async ({ tradingMode, errorMessage }) => {
|
||||||
|
const { result } = setup({
|
||||||
|
market: { ...defaultOrder.market, tradingMode },
|
||||||
|
orderType: OrderType.Market,
|
||||||
|
});
|
||||||
|
expect(result.current).toEqual(errorMessage);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each`
|
||||||
|
tradingMode | orderTimeInForce | errorMessage
|
||||||
|
${MarketTradingMode.BatchAuction} | ${OrderTimeInForce.FOK} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||||
|
${MarketTradingMode.MonitoringAuction} | ${OrderTimeInForce.FOK} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||||
|
${MarketTradingMode.OpeningAuction} | ${OrderTimeInForce.FOK} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||||
|
${MarketTradingMode.BatchAuction} | ${OrderTimeInForce.IOC} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||||
|
${MarketTradingMode.MonitoringAuction} | ${OrderTimeInForce.IOC} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||||
|
${MarketTradingMode.OpeningAuction} | ${OrderTimeInForce.IOC} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||||
|
${MarketTradingMode.BatchAuction} | ${OrderTimeInForce.GFN} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||||
|
${MarketTradingMode.MonitoringAuction} | ${OrderTimeInForce.GFN} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||||
|
${MarketTradingMode.OpeningAuction} | ${OrderTimeInForce.GFN} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||||
|
`(
|
||||||
|
'Returns an error message when submitting a limit order with a "$orderTimeInForce" value to a "$tradingMode" market',
|
||||||
|
async ({ tradingMode, orderTimeInForce, errorMessage }) => {
|
||||||
|
const { result } = setup({
|
||||||
|
market: { ...defaultOrder.market, tradingMode },
|
||||||
|
orderType: OrderType.Limit,
|
||||||
|
orderTimeInForce,
|
||||||
|
});
|
||||||
|
expect(result.current).toEqual(errorMessage);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each`
|
||||||
|
fieldName | errorType | errorMessage
|
||||||
|
${'size'} | ${'required'} | ${ERROR.FIELD_SIZE_REQ}
|
||||||
|
${'size'} | ${'min'} | ${ERROR.FIELD_SIZE_MIN}
|
||||||
|
${'price'} | ${'required'} | ${ERROR.FIELD_PRICE_REQ}
|
||||||
|
${'price'} | ${'min'} | ${ERROR.FIELD_PRICE_MIN}
|
||||||
|
`(
|
||||||
|
'Returns an error message when the order $fieldName "$errorType" validation fails',
|
||||||
|
async ({ fieldName, errorType, errorMessage }) => {
|
||||||
|
const { result } = setup({
|
||||||
|
fieldErrors: { [fieldName]: { type: errorType } },
|
||||||
|
});
|
||||||
|
expect(result.current).toEqual(errorMessage);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it('Returns an error message when the order size incorrectly has decimal values', async () => {
|
||||||
|
const { result } = setup({
|
||||||
|
market: { ...market, positionDecimalPlaces: 0 },
|
||||||
|
fieldErrors: { size: { type: 'validate', message: ERROR_SIZE_DECIMAL } },
|
||||||
|
});
|
||||||
|
expect(result.current).toEqual(ERROR.FIELD_PRICE_STEP_NULL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Returns an error message when the order size has more decimals then allowed', async () => {
|
||||||
|
const { result } = setup({
|
||||||
|
fieldErrors: { size: { type: 'validate', message: ERROR_SIZE_DECIMAL } },
|
||||||
|
});
|
||||||
|
expect(result.current).toEqual(ERROR.FIELD_PRICE_STEP_DECIMAL);
|
||||||
|
});
|
114
libs/deal-ticket/src/hooks/use-order-validation.tsx
Normal file
114
libs/deal-ticket/src/hooks/use-order-validation.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import type { FieldErrors } from 'react-hook-form';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import {
|
||||||
|
useVegaWallet,
|
||||||
|
OrderTimeInForce,
|
||||||
|
OrderType,
|
||||||
|
} from '@vegaprotocol/wallet';
|
||||||
|
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||||
|
import type { Order } from '../utils/get-default-order';
|
||||||
|
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||||
|
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
|
||||||
|
|
||||||
|
export type ValidationProps = {
|
||||||
|
step: number;
|
||||||
|
market: DealTicketQuery_market;
|
||||||
|
orderType: OrderType;
|
||||||
|
orderTimeInForce: OrderTimeInForce;
|
||||||
|
fieldErrors?: FieldErrors<Order>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useOrderValidation = ({
|
||||||
|
step,
|
||||||
|
market,
|
||||||
|
fieldErrors = {},
|
||||||
|
orderType,
|
||||||
|
orderTimeInForce,
|
||||||
|
}: ValidationProps) => {
|
||||||
|
const { keypair } = useVegaWallet();
|
||||||
|
|
||||||
|
const invalidText = useMemo(() => {
|
||||||
|
if (!keypair) {
|
||||||
|
return t('No public key selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keypair.tainted) {
|
||||||
|
return t('Selected public key has been tainted');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (market.state !== MarketState.Active) {
|
||||||
|
if (market.state === MarketState.Suspended) {
|
||||||
|
return t('Market is currently suspended');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
market.state === MarketState.Proposed ||
|
||||||
|
market.state === MarketState.Pending
|
||||||
|
) {
|
||||||
|
return t('Market is not active yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
return t('Market is no longer active');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (market.tradingMode !== MarketTradingMode.Continuous) {
|
||||||
|
if (orderType !== OrderType.Limit) {
|
||||||
|
return t('Only limit orders are permitted when market is in auction');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
OrderTimeInForce.FOK,
|
||||||
|
OrderTimeInForce.IOC,
|
||||||
|
OrderTimeInForce.GFN,
|
||||||
|
].includes(orderTimeInForce)
|
||||||
|
) {
|
||||||
|
return t(
|
||||||
|
'Only GTT, GTC and GFA are permitted when market is in auction'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldErrors?.size?.type === 'required') {
|
||||||
|
return t('An amount needs to be provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldErrors?.size?.type === 'min') {
|
||||||
|
return t(`The amount cannot be lower than "${step}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldErrors?.price?.type === 'required') {
|
||||||
|
return t('A price needs to be provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldErrors?.price?.type === 'min') {
|
||||||
|
return t(`The price cannot be negative`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
fieldErrors?.size?.type === 'validate' &&
|
||||||
|
fieldErrors?.size?.message === ERROR_SIZE_DECIMAL
|
||||||
|
) {
|
||||||
|
if (market.positionDecimalPlaces === 0) {
|
||||||
|
return t('No decimal amounts allowed for this order');
|
||||||
|
}
|
||||||
|
return t(
|
||||||
|
`The amount field only takes up to ${market.positionDecimalPlaces} decimals`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}, [
|
||||||
|
keypair,
|
||||||
|
step,
|
||||||
|
market,
|
||||||
|
fieldErrors?.size?.type,
|
||||||
|
fieldErrors?.size?.message,
|
||||||
|
fieldErrors?.price?.type,
|
||||||
|
orderType,
|
||||||
|
orderTimeInForce,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return invalidText;
|
||||||
|
};
|
@ -1,15 +1,16 @@
|
|||||||
export * from './expiry-selector';
|
export * from './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';
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import type { Order } from './use-order-state';
|
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
|
||||||
import type { TransactionStatus } from './deal-ticket';
|
|
||||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
|
||||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
|
||||||
|
|
||||||
interface SubmitButtonProps {
|
|
||||||
transactionStatus: TransactionStatus;
|
|
||||||
market: DealTicketQuery_market;
|
|
||||||
order: Order;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SubmitButton = ({
|
|
||||||
market,
|
|
||||||
transactionStatus,
|
|
||||||
order,
|
|
||||||
}: SubmitButtonProps) => {
|
|
||||||
const { keypair } = useVegaWallet();
|
|
||||||
|
|
||||||
const invalidText = useMemo(() => {
|
|
||||||
if (!keypair) {
|
|
||||||
return t('No public key selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keypair.tainted) {
|
|
||||||
return t('Selected public key has been tainted');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (market.state !== MarketState.Active) {
|
|
||||||
if (market.state === MarketState.Suspended) {
|
|
||||||
return t('Market is currently suspended');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
market.state === MarketState.Proposed ||
|
|
||||||
market.state === MarketState.Pending
|
|
||||||
) {
|
|
||||||
return t('Market is not active yet');
|
|
||||||
}
|
|
||||||
|
|
||||||
return t('Market is no longer active');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (market.tradingMode !== MarketTradingMode.Continuous) {
|
|
||||||
if (order.type === OrderType.Market) {
|
|
||||||
return t('Only limit orders are permitted when market is in auction');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
OrderTimeInForce.FOK,
|
|
||||||
OrderTimeInForce.IOC,
|
|
||||||
OrderTimeInForce.GFN,
|
|
||||||
].includes(order.timeInForce)
|
|
||||||
) {
|
|
||||||
return t(
|
|
||||||
'Only GTT, GTC and GFA are permitted when market is in auction'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}, [keypair, market, order]);
|
|
||||||
|
|
||||||
const disabled = transactionStatus === 'pending' || Boolean(invalidText);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
className="w-full mb-8"
|
|
||||||
variant="primary"
|
|
||||||
type="submit"
|
|
||||||
disabled={disabled}
|
|
||||||
data-testid="place-order"
|
|
||||||
>
|
|
||||||
{transactionStatus === 'pending' ? t('Pending...') : t('Place order')}
|
|
||||||
</Button>
|
|
||||||
{invalidText && (
|
|
||||||
<InputError className="mb-8" data-testid="dealticket-error-message">
|
|
||||||
{invalidText}
|
|
||||||
</InputError>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,79 +0,0 @@
|
|||||||
import type { OrderSide } from '@vegaprotocol/wallet';
|
|
||||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
|
||||||
import { useState, useCallback } from 'react';
|
|
||||||
|
|
||||||
export interface Order {
|
|
||||||
size: string;
|
|
||||||
type: OrderType;
|
|
||||||
timeInForce: OrderTimeInForce;
|
|
||||||
side: OrderSide | null;
|
|
||||||
price?: string;
|
|
||||||
expiration?: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UpdateOrder = (order: Partial<Order>) => void;
|
|
||||||
|
|
||||||
export const useOrderState = (defaultOrder: Order): [Order, UpdateOrder] => {
|
|
||||||
const [order, setOrder] = useState<Order>(defaultOrder);
|
|
||||||
|
|
||||||
const updateOrder = useCallback((orderUpdate: Partial<Order>) => {
|
|
||||||
setOrder((curr) => {
|
|
||||||
// Type is switching to market so return new market order object with correct defaults
|
|
||||||
if (
|
|
||||||
orderUpdate.type === OrderType.Market &&
|
|
||||||
curr.type !== OrderType.Market
|
|
||||||
) {
|
|
||||||
// Check if provided TIF or current TIF is valid for a market order and default
|
|
||||||
// to IOC if its not
|
|
||||||
|
|
||||||
const isTifValid = (tif: OrderTimeInForce) => {
|
|
||||||
return tif === OrderTimeInForce.FOK || tif === OrderTimeInForce.IOC;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Default
|
|
||||||
let timeInForce = OrderTimeInForce.IOC;
|
|
||||||
|
|
||||||
if (orderUpdate.timeInForce) {
|
|
||||||
if (isTifValid(orderUpdate.timeInForce)) {
|
|
||||||
timeInForce = orderUpdate.timeInForce;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isTifValid(curr.timeInForce)) {
|
|
||||||
timeInForce = curr.timeInForce;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: orderUpdate.type,
|
|
||||||
size: orderUpdate.size || curr.size,
|
|
||||||
side: orderUpdate.side || curr.side,
|
|
||||||
timeInForce,
|
|
||||||
price: undefined,
|
|
||||||
expiration: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type is switching to limit so return new order object with correct defaults
|
|
||||||
if (
|
|
||||||
orderUpdate.type === OrderType.Limit &&
|
|
||||||
curr.type !== OrderType.Limit
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
type: orderUpdate.type,
|
|
||||||
size: orderUpdate.size || curr.size,
|
|
||||||
side: orderUpdate.side || curr.side,
|
|
||||||
timeInForce: orderUpdate.timeInForce || curr.timeInForce,
|
|
||||||
price: orderUpdate.price || '0',
|
|
||||||
expiration: orderUpdate.expiration || undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...curr,
|
|
||||||
...orderUpdate,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return [order, updateOrder];
|
|
||||||
};
|
|
28
libs/deal-ticket/src/utils/get-default-order.ts
Normal file
28
libs/deal-ticket/src/utils/get-default-order.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { OrderTimeInForce, OrderType, OrderSide } from '@vegaprotocol/wallet';
|
||||||
|
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||||
|
import { toDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
export type Order =
|
||||||
|
| {
|
||||||
|
size: string;
|
||||||
|
type: OrderType.Market;
|
||||||
|
timeInForce: OrderTimeInForce;
|
||||||
|
side: OrderSide;
|
||||||
|
price?: never;
|
||||||
|
expiration?: never;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
size: string;
|
||||||
|
type: OrderType.Limit;
|
||||||
|
timeInForce: OrderTimeInForce;
|
||||||
|
side: OrderSide;
|
||||||
|
price?: string;
|
||||||
|
expiration?: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDefaultOrder = (market: DealTicketQuery_market): Order => ({
|
||||||
|
type: OrderType.Market,
|
||||||
|
side: OrderSide.Buy,
|
||||||
|
timeInForce: OrderTimeInForce.IOC,
|
||||||
|
size: String(toDecimal(market.positionDecimalPlaces)),
|
||||||
|
});
|
12
libs/deal-ticket/src/utils/validate-size.ts
Normal file
12
libs/deal-ticket/src/utils/validate-size.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export const ERROR_SIZE_DECIMAL = 'step';
|
||||||
|
|
||||||
|
export const validateSize = (step: number) => {
|
||||||
|
const [, stepDecimals = ''] = String(step).split('.');
|
||||||
|
return (value: string) => {
|
||||||
|
const [, valueDecimals = ''] = value.split('.');
|
||||||
|
if (stepDecimals.length < valueDecimals.length) {
|
||||||
|
return ERROR_SIZE_DECIMAL;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
@ -8,7 +8,7 @@
|
|||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
"noPropertyAccessFromIndexSignature": true,
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export const FAUCETABLE = process.env['NX_VEGA_ENV'] !== 'MAINNET';
|
|
@ -41,6 +41,7 @@ beforeEach(() => {
|
|||||||
max: new BigNumber(20),
|
max: new BigNumber(20),
|
||||||
},
|
},
|
||||||
allowance: new BigNumber(30),
|
allowance: new BigNumber(30),
|
||||||
|
isFaucetable: true,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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" />
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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,17 +24,15 @@ 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}
|
open={open}
|
||||||
open={open}
|
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} onSelect={setClose} />
|
||||||
<SelectMarketList data={data} />
|
</Dialog>
|
||||||
</Dialog>
|
|
||||||
}
|
|
||||||
</AsyncRenderer>
|
</AsyncRenderer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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 = {
|
||||||
|
@ -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}
|
||||||
>
|
>
|
||||||
{marketName}
|
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||||
|
<a
|
||||||
|
onClick={() => onSelect(id)}
|
||||||
|
data-testid={`market-link-${id}`}
|
||||||
|
>
|
||||||
|
{marketName}
|
||||||
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td className={tdClassNames}>
|
<td className={tdClassNames}>
|
||||||
|
@ -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 {
|
||||||
@ -110,14 +94,14 @@ export interface MarketList_markets {
|
|||||||
/**
|
/**
|
||||||
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
|
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
|
||||||
* number denominated in the currency of the Market. (uint64)
|
* number denominated in the currency of the Market. (uint64)
|
||||||
*
|
*
|
||||||
* Examples:
|
* Examples:
|
||||||
* Currency Balance decimalPlaces Real Balance
|
* Currency Balance decimalPlaces Real Balance
|
||||||
* GBP 100 0 GBP 100
|
* GBP 100 0 GBP 100
|
||||||
* GBP 100 2 GBP 1.00
|
* GBP 100 2 GBP 1.00
|
||||||
* GBP 100 4 GBP 0.01
|
* GBP 100 4 GBP 0.01
|
||||||
* GBP 1 4 GBP 0.0001 ( 0.01p )
|
* GBP 1 4 GBP 0.0001 ( 0.01p )
|
||||||
*
|
*
|
||||||
* GBX (pence) 100 0 GBP 1.00 (100p )
|
* GBX (pence) 100 0 GBP 1.00 (100p )
|
||||||
* GBX (pence) 100 2 GBP 0.01 ( 1p )
|
* GBX (pence) 100 2 GBP 0.01 ( 1p )
|
||||||
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
|
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
|
||||||
|
@ -2,3 +2,4 @@ export * from './market-list-table';
|
|||||||
export * from './markets-container';
|
export * from './markets-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
Loading…
Reference in New Issue
Block a user