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

This commit is contained in:
madalinaraicu 2022-06-06 19:43:45 +01:00
commit d6f5ece80f
53 changed files with 1254 additions and 282 deletions

View File

@ -1,4 +1,7 @@
import '@testing-library/jest-dom';
import { defaultFallbackInView } from 'react-intersection-observer';
defaultFallbackInView(true);
Object.defineProperty(window, 'matchMedia', {
writable: true,

View File

@ -43,7 +43,7 @@ function App() {
<ApolloProvider client={client}>
<VegaWalletProvider>
<AppLoader>
<div className="h-full dark:bg-black dark:text-white-60 bg-white text-black-60 grid grid-rows-[min-content,1fr]">
<div className="max-h-full min-h-full dark:bg-black dark:text-white-60 bg-white text-black-60 grid grid-rows-[min-content,1fr]">
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
<DrawerToggle
onToggle={onToggle}

View File

@ -1,12 +1,15 @@
import type { DealTicketContainerProps } from '@vegaprotocol/deal-ticket';
import { useParams } from 'react-router-dom';
import {
DealTicketManager,
DealTicketContainer as Container,
} from '@vegaprotocol/deal-ticket';
import { DealTicketSteps } from './deal-ticket-steps';
export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => {
return (
const tempEmptyText = <p>Please select a market from the markets page</p>;
export const DealTicketContainer = () => {
const { marketId } = useParams<{ marketId: string }>();
return marketId ? (
<Container marketId={marketId}>
{(data) => (
<DealTicketManager market={data.market}>
@ -14,5 +17,7 @@ export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => {
</DealTicketManager>
)}
</Container>
) : (
tempEmptyText
);
};

View File

@ -70,7 +70,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
{
label: 'Select Asset',
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
component: <div />,
component: <h1 className="font-bold mb-16">{market.name}</h1>,
},
{
label: 'Select Order Type',

View File

@ -10,5 +10,5 @@ export const DrawerContainer = ({ children }: Props) => (
);
export const DrawerWrapper = ({ children }: Props) => (
<div className="flex">{children}</div>
<div className="flex dark:bg-black">{children}</div>
);

View File

@ -0,0 +1,27 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL subscription operation: CandleLive
// ====================================================
export interface CandleLive_candles {
__typename: "Candle";
/**
* Close price (uint64)
*/
close: string;
}
export interface CandleLive {
/**
* Subscribe to the candles updates
*/
candles: CandleLive_candles;
}
export interface CandleLiveVariables {
marketId: string;
}

View File

@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { Interval, MarketState } from "@vegaprotocol/types";
import { MarketState } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: SimpleMarkets
@ -117,6 +117,5 @@ export interface SimpleMarkets {
}
export interface SimpleMarketsVariables {
CandleInterval: Interval;
CandleSince: string;
}

View File

@ -18,9 +18,9 @@ const MARKET_DATA_FRAGMENT = gql`
}
`;
const MARKETS_QUERY = gql`
export const MARKETS_QUERY = gql`
${MARKET_DATA_FRAGMENT}
query SimpleMarkets($CandleInterval: Interval!, $CandleSince: String!) {
query SimpleMarkets($CandleSince: String!) {
markets {
id
name
@ -41,7 +41,7 @@ const MARKETS_QUERY = gql`
}
}
}
candles(interval: $CandleInterval, since: $CandleSince) {
candles(interval: I1H, since: $CandleSince) {
open
close
}
@ -58,6 +58,14 @@ const MARKET_DATA_SUB = gql`
}
`;
export const CANDLE_SUB = gql`
subscription CandleLive($marketId: ID!) {
candles(marketId: $marketId, interval: I1H) {
close
}
}
`;
const update = (
draft: SimpleMarkets_markets[],
delta: SimpleMarketDataSub_marketData

View File

@ -1,15 +1,22 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { MarketState } from '@vegaprotocol/types';
import SimpleMarketList from './simple-market-list';
import { MARKETS_QUERY } from './data-provider';
import type { SimpleMarkets_markets } from './__generated__/SimpleMarkets';
import type { SimpleMarkets } from './__generated__/SimpleMarkets';
jest.mock('./data-provider', () => jest.fn());
const mockedNavigate = jest.fn();
jest.mock('@vegaprotocol/react-helpers', () => ({
useDataProvider: jest.fn(),
t: (txt: string) => txt,
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedNavigate,
}));
jest.mock('date-fns', () => ({
subDays: () => new Date('2022-06-02T11:11:21.721Z'),
}));
describe('SimpleMarketList', () => {
@ -17,16 +24,31 @@ describe('SimpleMarketList', () => {
jest.clearAllMocks();
});
it('should be properly renderer as empty', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(useDataProvider as unknown as jest.SpyInstance<any>).mockImplementation(
() => ({ data: [], error: false, loading: false })
it('should be properly renderer as empty', async () => {
const mocks: MockedResponse<SimpleMarkets> = {
request: {
query: MARKETS_QUERY,
variables: {
CandleSince: '2022-06-02T11:11:21.721Z',
},
},
result: {
data: { markets: [] },
},
};
render(
<MockedProvider mocks={[mocks]}>
<SimpleMarketList />
</MockedProvider>
);
render(<SimpleMarketList />);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(screen.getByText('No data to display')).toBeInTheDocument();
});
it('should be properly rendered with some data', () => {
it('should be properly rendered with some data', async () => {
const data = [
{
id: '1',
@ -69,11 +91,26 @@ describe('SimpleMarketList', () => {
},
},
] as unknown as SimpleMarkets_markets[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(useDataProvider as unknown as jest.SpyInstance<any>).mockImplementation(
() => ({ data, error: false, loading: false })
const mocks: MockedResponse<SimpleMarkets> = {
request: {
query: MARKETS_QUERY,
variables: {
CandleSince: '2022-06-02T11:11:21.721Z',
},
},
result: {
data: { markets: data },
},
};
render(
<MockedProvider mocks={[mocks]}>
<SimpleMarketList />
</MockedProvider>
);
render(<SimpleMarketList />);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(screen.getByRole('list')).toBeInTheDocument();
expect(screen.getAllByRole('listitem')).toHaveLength(2);
});

View File

@ -1,30 +1,55 @@
import React, { useCallback, useMemo } from 'react';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { subDays } from 'date-fns';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/react-helpers';
import { AsyncRenderer, Lozenge, Splash } from '@vegaprotocol/ui-toolkit';
import { Button } from '@vegaprotocol/ui-toolkit';
import type { MarketState } from '@vegaprotocol/types';
import SimpleMarketPercentChange from './simple-market-percent-change';
import SimpleMarketExpires from './simple-market-expires';
import DataProvider from './data-provider';
import { MARKET_STATUS } from './constants';
const SimpleMarketList = () => {
const navigate = useNavigate();
const statusesRef = useRef<Record<string, MarketState | ''>>({});
const variables = useMemo(
() => ({
CandleInterval: 'I1H',
CandleSince: new Date(Date.now() - 24 * 60 * 60 * 1000).toJSON(),
CandleSince: subDays(Date.now(), 1).toJSON(),
}),
[]
);
const update = useCallback(
(delta) => {
if (statusesRef.current[delta.market.id] !== delta.market.state) {
return false;
}
return true;
},
[statusesRef]
);
const { data, error, loading } = useDataProvider(
DataProvider,
undefined, // @TODO - if we need a live update in the future
update,
variables
);
const onClick = useCallback((marketId) => {
// @TODO - let's try to have navigation first
console.log('trigger market', marketId);
}, []);
useEffect(() => {
const statuses: Record<string, MarketState | ''> = {};
data?.forEach((market) => {
statuses[market.id] = market.data?.market.state || '';
});
statusesRef.current = statuses;
}, [data]);
const onClick = useCallback(
(marketId) => {
navigate(`/trading/${marketId}`);
},
[navigate]
);
return (
<AsyncRenderer loading={loading} error={error} data={data}>
{data && data.length > 0 ? (
@ -47,7 +72,10 @@ const SimpleMarketList = () => {
</div>
<div className="w-full grid sm:grid-rows-2">
<div>
<SimpleMarketPercentChange candles={market.candles} />
<SimpleMarketPercentChange
candles={market.candles}
marketId={market.id}
/>
</div>
<div>
<Lozenge

View File

@ -1,6 +1,7 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { theme } from '@vegaprotocol/tailwindcss-config';
import { MockedProvider } from '@apollo/react-testing';
import SimpleMarketPercentChange from './simple-market-percent-change';
import type { SimpleMarkets_markets_candles } from './__generated__/SimpleMarkets';
@ -8,12 +9,20 @@ describe('SimpleMarketPercentChange should parse proper change', () => {
let candles: (SimpleMarkets_markets_candles | null)[] | null;
it('empty array', () => {
candles = [];
render(<SimpleMarketPercentChange candles={candles} />);
render(
<MockedProvider>
<SimpleMarketPercentChange candles={candles} marketId={'1'} />
</MockedProvider>
);
expect(screen.getByText('-')).toBeInTheDocument();
});
it('null', () => {
candles = null;
render(<SimpleMarketPercentChange candles={candles} />);
render(
<MockedProvider>
<SimpleMarketPercentChange candles={candles} marketId={'1'} />
</MockedProvider>
);
expect(screen.getByText('-')).toBeInTheDocument();
});
it('an appreciated one', () => {
@ -22,7 +31,11 @@ describe('SimpleMarketPercentChange should parse proper change', () => {
{ close: '100' } as SimpleMarkets_markets_candles,
null,
];
render(<SimpleMarketPercentChange candles={candles} />);
render(
<MockedProvider>
<SimpleMarketPercentChange candles={candles} marketId={'1'} />
</MockedProvider>
);
expect(screen.getByText('100.000%')).toBeInTheDocument();
expect(screen.getByText('100.000%')).toHaveStyle(
`color: ${theme.colors.vega.green}`
@ -34,7 +47,11 @@ describe('SimpleMarketPercentChange should parse proper change', () => {
{ close: '50' } as SimpleMarkets_markets_candles,
null,
];
render(<SimpleMarketPercentChange candles={candles} />);
render(
<MockedProvider>
<SimpleMarketPercentChange candles={candles} marketId={'1'} />
</MockedProvider>
);
expect(screen.getByText('-50.000%')).toBeInTheDocument();
expect(screen.getByText('-50.000%')).toHaveStyle(
`color: ${theme.colors.vega.pink}`

View File

@ -1,22 +1,34 @@
import React from 'react';
import React, { useState } from 'react';
import { InView } from 'react-intersection-observer';
import { useSubscription } from '@apollo/client';
import { theme } from '@vegaprotocol/tailwindcss-config';
import type { SimpleMarkets_markets_candles } from './__generated__/SimpleMarkets';
import type {
CandleLive,
CandleLiveVariables,
} from './__generated__/CandleLive';
import { CANDLE_SUB } from './data-provider';
interface Props {
candles: (SimpleMarkets_markets_candles | null)[] | null;
marketId: string;
}
const getChange = (
candles: (SimpleMarkets_markets_candles | null)[] | null
candles: (SimpleMarkets_markets_candles | null)[] | null,
lastClose?: string
) => {
if (candles) {
const first = parseInt(candles.find((item) => item?.open)?.open || '-1');
const last = candles.reduceRight((aggr, item) => {
if (aggr === -1 && item?.close) {
aggr = parseInt(item.close);
}
return aggr;
}, -1);
const last =
typeof lastClose === 'undefined'
? candles.reduceRight((aggr, item) => {
if (aggr === -1 && item?.close) {
aggr = parseInt(item.close);
}
return aggr;
}, -1)
: parseInt(lastClose);
if (first !== -1 && last !== -1) {
return Number(((last - first) / first) * 100).toFixed(3) + '%';
}
@ -34,10 +46,25 @@ const getColor = (change: number | string) => {
return theme.colors.intent.highlight;
};
const SimpleMarketPercentChange = ({ candles }: Props) => {
const change = getChange(candles);
const SimpleMarketPercentChangeWrapper = (props: Props) => {
const [inView, setInView] = useState(false);
return (
// @ts-ignore falsy wrong type?
<InView onChange={setInView}>
{inView && <SimpleMarketPercentChange {...props} />}
</InView>
);
};
const SimpleMarketPercentChange = ({ candles, marketId }: Props) => {
const { data: { candles: { close = undefined } = {} } = {} } =
useSubscription<CandleLive, CandleLiveVariables>(CANDLE_SUB, {
variables: { marketId },
});
const change = getChange(candles, close);
const color = getColor(change);
return <p style={{ color }}>{change}</p>;
};
export default SimpleMarketPercentChange;
export default SimpleMarketPercentChangeWrapper;

View File

@ -1,6 +1,15 @@
import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client';
import {
ApolloClient,
from,
HttpLink,
InMemoryCache,
split,
} from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient as createWSClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';
export function createClient(base?: string) {
if (!base) {
@ -40,6 +49,24 @@ export function createClient(base?: string) {
credentials: 'same-origin',
});
const wsLink = new GraphQLWsLink(
createWSClient({
url: urlWS.href,
})
);
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
const errorLink = onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors);
console.log(networkError);
@ -47,7 +74,7 @@ export function createClient(base?: string) {
return new ApolloClient({
connectToDevTools: process.env['NODE_ENV'] === 'development',
link: from([errorLink, retryLink, httpLink]),
link: from([errorLink, retryLink, splitLink]),
cache,
});
}

View File

@ -28,13 +28,13 @@ export const routerConfig = [
path: ROUTES.TRADING,
name: 'Trading',
text: t('Trading'),
element: (
<DealTicketContainer
marketId={
'41013c28d53a72225c07cf2660cdd415d9dd0e9317ec4574e77592332db35596'
}
/>
),
element: <DealTicketContainer />,
children: [
{
path: ':marketId',
element: <DealTicketContainer />,
},
],
},
{
path: ROUTES.LIQUIDITY,

View File

@ -38,7 +38,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45",
"total_removed": "0",
"locked_amount": "129999.45",
"locked_amount": "129682.36162978697785023",
"deposits": [
{
"amount": "129999.45",
@ -488,7 +488,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "97499.58",
"total_removed": "0",
"locked_amount": "71359.360754532949904994",
"locked_amount": "70428.517923311130372024",
"deposits": [
{
"amount": "97499.58",
@ -521,7 +521,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "135173.4239508",
"total_removed": "0",
"locked_amount": "97535.92647176062142134244004",
"locked_amount": "96263.62502479102429967123688",
"deposits": [
{
"amount": "135173.4239508",
@ -554,7 +554,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "32499.86",
"total_removed": "0",
"locked_amount": "30019.65075918605161088",
"locked_amount": "29628.061254886910252894",
"deposits": [
{
"amount": "32499.86",
@ -587,7 +587,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "10833.29",
"total_removed": "0",
"locked_amount": "9771.105018319376583514",
"locked_amount": "9643.646434564611031407",
"deposits": [
{
"amount": "10833.29",
@ -675,7 +675,7 @@
"tranche_end": "2022-11-01T00:00:00.000Z",
"total_added": "22500",
"total_removed": "0",
"locked_amount": "18545.82059556159525",
"locked_amount": "18056.6590240036215",
"deposits": [
{
"amount": "15000",
@ -761,7 +761,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38",
"total_removed": "0",
"locked_amount": "1938140.454506446441067284",
"locked_amount": "1916879.612086597542469696",
"deposits": [
{
"amount": "1852091.69",
@ -1776,8 +1776,8 @@
"tranche_start": "2021-09-05T00:00:00.000Z",
"tranche_end": "2022-09-30T00:00:00.000Z",
"total_added": "60916.66666633337",
"total_removed": "17568.575895506846757997",
"locked_amount": "18691.0451412597139137896684208311",
"total_removed": "17896.108295511846757997",
"locked_amount": "18066.219044199191697914984957962",
"deposits": [
{
"amount": "2833.333333",
@ -1876,6 +1876,11 @@
}
],
"withdrawals": [
{
"amount": "327.532400005",
"user": "0x1887D97F9C875108Aa6bE109B282f87A666472f2",
"tx": "0x6002d50af16df494528c32126c3cb569f105ee8118a3f48a34e8883037132198"
},
{
"amount": "490.9767850775",
"user": "0x9fd50776F133751E8Ae6abE1Be124638Bb917E05",
@ -2493,6 +2498,12 @@
}
],
"withdrawals": [
{
"amount": "327.532400005",
"user": "0x1887D97F9C875108Aa6bE109B282f87A666472f2",
"tranche_id": 13,
"tx": "0x6002d50af16df494528c32126c3cb569f105ee8118a3f48a34e8883037132198"
},
{
"amount": "327.7316817825",
"user": "0x1887D97F9C875108Aa6bE109B282f87A666472f2",
@ -2555,8 +2566,8 @@
}
],
"total_tokens": "4250",
"withdrawn_tokens": "2653.6331018125",
"remaining_tokens": "1596.3668981875"
"withdrawn_tokens": "2981.1655018175",
"remaining_tokens": "1268.8344981825"
},
{
"address": "0x9fd50776F133751E8Ae6abE1Be124638Bb917E05",
@ -5040,8 +5051,8 @@
"tranche_start": "2021-09-03T00:00:00.000Z",
"tranche_end": "2022-09-03T00:00:00.000Z",
"total_added": "15073.000000000000000003",
"total_removed": "2897.73152892253",
"locked_amount": "3826.6258515664639083007616186263318113",
"total_removed": "2905.98379934253",
"locked_amount": "3661.43177476534672755072873982115677305",
"deposits": [
{
"amount": "10",
@ -7685,6 +7696,11 @@
}
],
"withdrawals": [
{
"amount": "8.25227042",
"user": "0xc5467213593778E528f0eB8117cc7AFBC5b7491b",
"tx": "0xce562db43fe92ccd76b20c462f4f680336542c064020dfb646512994c4f229d9"
},
{
"amount": "7.42538686",
"user": "0xc5467213593778E528f0eB8117cc7AFBC5b7491b",
@ -12837,6 +12853,12 @@
}
],
"withdrawals": [
{
"amount": "8.25227042",
"user": "0xc5467213593778E528f0eB8117cc7AFBC5b7491b",
"tranche_id": 11,
"tx": "0xce562db43fe92ccd76b20c462f4f680336542c064020dfb646512994c4f229d9"
},
{
"amount": "7.42538686",
"user": "0xc5467213593778E528f0eB8117cc7AFBC5b7491b",
@ -12881,8 +12903,8 @@
}
],
"total_tokens": "200",
"withdrawn_tokens": "142.68559107",
"remaining_tokens": "57.31440893"
"withdrawn_tokens": "150.93786149",
"remaining_tokens": "49.06213851"
},
{
"address": "0x1775cc97E5c05Fde8b571ef75CA52d0A9ff19025",
@ -12907,7 +12929,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671",
"total_removed": "74162.9780761646031",
"locked_amount": "3002748.41446940756547250008",
"locked_amount": "2970077.9019092314063579103",
"deposits": [
{
"amount": "1998.95815",
@ -13620,7 +13642,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15788853.065470999700000001",
"total_removed": "0",
"locked_amount": "15788853.065470999700000001",
"locked_amount": "15750341.6587991785653755639256064283711814",
"deposits": [
{
"amount": "16249.93",
@ -15571,8 +15593,8 @@
"tranche_start": "2021-11-05T00:00:00.000Z",
"tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999",
"total_removed": "1563001.535509480008878233",
"locked_amount": "9000945.53475187470322777308704175",
"total_removed": "2039274.435730582717034572",
"locked_amount": "8893995.82561804098467636432418579",
"deposits": [
{
"amount": "129284.449",
@ -15781,6 +15803,11 @@
}
],
"withdrawals": [
{
"amount": "652.48254356494551875",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x4a972ce40877c56aaa0fb53f479666723bf6a89ea9df5a18ffeffb2580d416d4"
},
{
"amount": "1384.357697656285885",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@ -15916,6 +15943,26 @@
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x3f1666dc9c401996a37b4543b7945d14f6d66561cd1d0a84caa30b92fc55408e"
},
{
"amount": "755.451613460173812",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x41ce3e549c72e49df5ebe782512c14113e7df9258c9d4c8bccd2dcb654fbb898"
},
{
"amount": "31474.290919355234375",
"user": "0xDdc5F6484349B242A9B23382Aa21f49B5Dc9a089",
"tx": "0x34be40dc6242c39c9fefcebcdf7f09167297c0a3e099e9b76428758a9953fd48"
},
{
"amount": "442758.921918885747690339",
"user": "0xa8679b60612Fb2e19d68964326CA02dCe6a08D08",
"tx": "0x232e5795842832a7105c1e8cff4125da02020c1b348758998f06d71fbd674a9d"
},
{
"amount": "631.75322583660676025",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x9d31de2586ae4a6ce3525a15347349febfd085aa6e202b9641621e12e58909e2"
},
{
"amount": "58127.81687116447134108",
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
@ -17237,6 +17284,12 @@
}
],
"withdrawals": [
{
"amount": "652.48254356494551875",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0x4a972ce40877c56aaa0fb53f479666723bf6a89ea9df5a18ffeffb2580d416d4"
},
{
"amount": "1384.357697656285885",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@ -17327,6 +17380,18 @@
"tranche_id": 3,
"tx": "0x3f1666dc9c401996a37b4543b7945d14f6d66561cd1d0a84caa30b92fc55408e"
},
{
"amount": "755.451613460173812",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0x41ce3e549c72e49df5ebe782512c14113e7df9258c9d4c8bccd2dcb654fbb898"
},
{
"amount": "631.75322583660676025",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0x9d31de2586ae4a6ce3525a15347349febfd085aa6e202b9641621e12e58909e2"
},
{
"amount": "673.86655774325828525",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@ -18349,8 +18414,8 @@
}
],
"total_tokens": "359123.469575",
"withdrawn_tokens": "136988.021403105210273",
"remaining_tokens": "222135.448171894789727"
"withdrawn_tokens": "139027.708785966936364",
"remaining_tokens": "220095.760789033063636"
},
{
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
@ -18795,6 +18860,12 @@
}
],
"withdrawals": [
{
"amount": "31474.290919355234375",
"user": "0xDdc5F6484349B242A9B23382Aa21f49B5Dc9a089",
"tranche_id": 3,
"tx": "0x34be40dc6242c39c9fefcebcdf7f09167297c0a3e099e9b76428758a9953fd48"
},
{
"amount": "250551.564697895841552119",
"user": "0xDdc5F6484349B242A9B23382Aa21f49B5Dc9a089",
@ -18809,8 +18880,8 @@
}
],
"total_tokens": "805741.847535494",
"withdrawn_tokens": "278425.719380526241012802",
"remaining_tokens": "527316.128154967758987198"
"withdrawn_tokens": "309900.010299881475387802",
"remaining_tokens": "495841.837235612524612198"
},
{
"address": "0x47FbE34Fd93416c63d35544dEE6c6f442Db8513d",
@ -19068,10 +19139,17 @@
"tx": "0xff922daf92e19ea138491989d06d76cc4e92011c1cbf2b83e3caebe7179594fb"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "442758.921918885747690339",
"user": "0xa8679b60612Fb2e19d68964326CA02dCe6a08D08",
"tranche_id": 3,
"tx": "0x232e5795842832a7105c1e8cff4125da02020c1b348758998f06d71fbd674a9d"
}
],
"total_tokens": "1151073.5946347",
"withdrawn_tokens": "0",
"remaining_tokens": "1151073.5946347"
"withdrawn_tokens": "442758.921918885747690339",
"remaining_tokens": "708314.672715814252309661"
},
{
"address": "0x97E5985117F47c8d110Be1c422DdCB9bE9b46e62",
@ -19321,8 +19399,8 @@
"tranche_start": "2021-10-05T00:00:00.000Z",
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303",
"total_removed": "1292362.811321297942666742",
"locked_amount": "3239424.61450565009103150955191263",
"total_removed": "1341050.833974497576969742",
"locked_amount": "3197168.13760041683379941673182373",
"deposits": [
{
"amount": "552496.6455",
@ -19486,6 +19564,11 @@
"user": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3",
"tx": "0x8e9f65bfea45a61e4f20108bc0ad1ce2bdeba8e355c7b1d4e024a3ffd73065a4"
},
{
"amount": "48688.022653199634303",
"user": "0xa8679b60612Fb2e19d68964326CA02dCe6a08D08",
"tx": "0xd6949910fc00a00de948bf09b7d8d294d0bda6f50c960432679cfd18bd15cd18"
},
{
"amount": "7013.412182841867109",
"user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75",
@ -19753,10 +19836,17 @@
"tx": "0x9d3432b818054796489848c415af5c523acb16c1540e5865010baf71964c03a7"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "48688.022653199634303",
"user": "0xa8679b60612Fb2e19d68964326CA02dCe6a08D08",
"tranche_id": 4,
"tx": "0xd6949910fc00a00de948bf09b7d8d294d0bda6f50c960432679cfd18bd15cd18"
}
],
"total_tokens": "110499.5291",
"withdrawn_tokens": "0",
"remaining_tokens": "110499.5291"
"withdrawn_tokens": "48688.022653199634303",
"remaining_tokens": "61811.506446800365697"
},
{
"address": "0x8767d65677CabaD2050b764AEf40610f2f9796F5",
@ -20366,7 +20456,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996",
"total_removed": "0",
"locked_amount": "472355.6199999996",
"locked_amount": "470625.82124660851890028549873164",
"deposits": [
{
"amount": "3000",
@ -46019,8 +46109,8 @@
"tranche_start": "2021-12-05T00:00:00.000Z",
"tranche_end": "2022-06-05T00:00:00.000Z",
"total_added": "171288.42",
"total_removed": "17067.9263679026803",
"locked_amount": "2506.83382533958609789446",
"total_removed": "20783.0438075488377",
"locked_amount": "0",
"deposits": [
{
"amount": "250",
@ -50244,6 +50334,116 @@
}
],
"withdrawals": [
{
"amount": "183.6335597275",
"user": "0x690Fc36d52eD3f198F0eBDea1557333a1766f786",
"tx": "0xba954a4d4b886c981f4965fe7da9c29120a6f1b44c4ce73a94bd7f2eff690434"
},
{
"amount": "183.6407458275",
"user": "0xb7f112a2Ff7984Ef494E87b2539059d77d500f37",
"tx": "0x66ecf2cabcc78741ac276ace894a5d203475284a7dbaa3df624dd9765b5e34f8"
},
{
"amount": "183.6396170375",
"user": "0x391484aff3906c940325C63Cf69dC5509710eA6a",
"tx": "0x70e570adefd4935fcf33ef02e007a3f584a55def453b8d3cddba657e21858af5"
},
{
"amount": "249.9983783575",
"user": "0xf0352681BcDBA8296b716d4EE085C27433Fff690",
"tx": "0x65af2b3830875fac6eddb4ccdbaa62a6a43c9be071af5c4a201742704141e388"
},
{
"amount": "34.2757777525",
"user": "0x967C162B564c83A30509CB77e572A98e1150a9D3",
"tx": "0x41d9be48e1130b253b786db21c3d6f8ab169114251b3a3866ce6e2d4ef1870e0"
},
{
"amount": "23.0332214085",
"user": "0x8433BF349d4f71FB37c8CacF5D6D4863D43EaE23",
"tx": "0xf55524a47a3b15876ef7965636d7caff9a0bb11566f359998bf4c79afad22673"
},
{
"amount": "46.069991352",
"user": "0x2814A08930732406a99139B6856001023239F9Dd",
"tx": "0xa047a1c664e5c65286e3e4470d85497376315623a6be3c7f9289d1484d2209dc"
},
{
"amount": "120",
"user": "0xA301F00b655a45D454747B4b8fb3dcEFD8C4D66f",
"tx": "0x7f3a81b45debc735d8c383ed438f92627b2f2a3021b92ffc3db4c6d8261f28f2"
},
{
"amount": "25.40420228",
"user": "0xfeF4b56A370135A17Eef5ba8f5c308451a64Cf86",
"tx": "0x6ecd654e1aeecdcc7073e1ec881b5b0fe4e24df9e9e226470aa35671e5c0be29"
},
{
"amount": "157.270935085",
"user": "0x2740029049CF5d991202DeEd75965C6F34Aa36B9",
"tx": "0xe613bfdfe6d2dbbff73e7089139458b2d905150649739392e067a652d3226557"
},
{
"amount": "250",
"user": "0x7b82f39c7AC83442EE6b025D5B69b4A49E6FD168",
"tx": "0xffcba1d72b13cb6fe9c5cf269b956cf55d54367d1dbb2d48ba6c03a2b94e9434"
},
{
"amount": "250",
"user": "0x02B5Fd5A74367D192b807Da32EaF1adF5E683573",
"tx": "0xc833bf14e1891f661410ddafd4f97f009f63d4ac2ca906d19c74cf2223ff430a"
},
{
"amount": "250",
"user": "0xBD352479242D160FaEeFB21422786C5B201A1cEc",
"tx": "0x90a9ff32dac087d7f7a7050ee67a9fdf3fe2cc2dacf5f1e900dac53b9741fd68"
},
{
"amount": "250",
"user": "0x9b427a2EA3536FbFcD37E48c9EC798ea11E2Ec87",
"tx": "0xe5355d8976bf4d26018fea411fb0f01cbc1c43ebda8ad48970df81ae1ef7e8d6"
},
{
"amount": "250",
"user": "0xe87aE24a4bE0158f25DFC6733c48177C86998B6F",
"tx": "0x98625ddb4f277e915fd5478dc6f97b22ec9793a668bba209c9c82805da44f403"
},
{
"amount": "250",
"user": "0x5417806fA87Be7F9bA7aEf4FFB9277E8922Fd9BE",
"tx": "0x1c8ed1ce8127a594747ae20827659e12715af4dae9f855a8b641c1b35412b8a3"
},
{
"amount": "0.0016216425",
"user": "0xf0352681BcDBA8296b716d4EE085C27433Fff690",
"tx": "0xbe02347c087aaed316855eca5a01116fc178087524e3e1885d20c0b119a2af2a"
},
{
"amount": "250",
"user": "0xD1C87a90455f2765bE275c506738C026B011a6C8",
"tx": "0xf19508bf8446e4c1bf9cf879df87087a3c0ee57a175ecae7a75e4a6442c08270"
},
{
"amount": "150",
"user": "0x92f15CBCE433FDcA0a87FD778e5BBbD0B3d7eE0f",
"tx": "0x2638a870214b1d904c8e8e80cf35d4bc1b382801b9c16239af41ca2cf06a1654"
},
{
"amount": "250",
"user": "0xc8aC50a6BA786450676f278Aa716Eb2bA7249B22",
"tx": "0x91d6f0ea8c0c19d00a1c08a53fdf7e839a35fce2c3a80ce993f058c0b4ae9cac"
},
{
"amount": "29.9837040875",
"user": "0xF047Ee0434548d5E2cA1F080ba6208A22C97d290",
"tx": "0xe2885fb42ec26c0d8964c772f19eff356947add7b0b44f1cb7a3d49c0fdc830b"
},
{
"amount": "64.1735506975",
"user": "0xe8F33102aDD808E841268E4161326C76A3D31d24",
"tx": "0xaf108d45b68929af7d63e571bf6f4d9d2415437c95470d70d70786398aa192ab"
},
{
"amount": "65.56208982",
"user": "0x36d345DCEB35816AD557355fA51E7C99FEE7d18E",
@ -50339,6 +50539,16 @@
"user": "0x27049a430Df8b89Ae4f899b0383B0C876F9cAcEb",
"tx": "0x2dadd46cff92cdcb974ba556776f69b136a526e05391e07618503526ef3667e0"
},
{
"amount": "17.2834799106574",
"user": "0x34D4Da6a0829607fFAb8C0aFfDf6aBe059d89b86",
"tx": "0x9d5a7c722b96c3a94457ca95d2152c3decdaf3ae89634a7503b0ff1e131d2960"
},
{
"amount": "246.70865448",
"user": "0x00Ddf1483dEc8E5A38D0fFE78b6C14D0954D255e",
"tx": "0xd1c9e882269eee55b067744c783ed78b875c7f3602bac5e8ed0e7c0b1fe623e6"
},
{
"amount": "252.08931115",
"user": "0xE245503f8a33eBDDD718915132869C9Ba9df37A8",
@ -51691,10 +51901,17 @@
"tx": "0xee02e72ef8e0755982704385da512cc12c8ff0ea7b58056fa3d5a5932394e9ca"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0xD1C87a90455f2765bE275c506738C026B011a6C8",
"tranche_id": 6,
"tx": "0xf19508bf8446e4c1bf9cf879df87087a3c0ee57a175ecae7a75e4a6442c08270"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x3A7983a6e8AaC25E5DeB6Bf1D0F21c8b8a17D1a6",
@ -54263,6 +54480,12 @@
}
],
"withdrawals": [
{
"amount": "183.6335597275",
"user": "0x690Fc36d52eD3f198F0eBDea1557333a1766f786",
"tranche_id": 6,
"tx": "0xba954a4d4b886c981f4965fe7da9c29120a6f1b44c4ce73a94bd7f2eff690434"
},
{
"amount": "66.325088395",
"user": "0x690Fc36d52eD3f198F0eBDea1557333a1766f786",
@ -54271,8 +54494,8 @@
}
],
"total_tokens": "250",
"withdrawn_tokens": "66.325088395",
"remaining_tokens": "183.674911605"
"withdrawn_tokens": "249.9586481225",
"remaining_tokens": "0.0413518775"
},
{
"address": "0x391484aff3906c940325C63Cf69dC5509710eA6a",
@ -54285,6 +54508,12 @@
}
],
"withdrawals": [
{
"amount": "183.6396170375",
"user": "0x391484aff3906c940325C63Cf69dC5509710eA6a",
"tranche_id": 6,
"tx": "0x70e570adefd4935fcf33ef02e007a3f584a55def453b8d3cddba657e21858af5"
},
{
"amount": "66.3216225325",
"user": "0x391484aff3906c940325C63Cf69dC5509710eA6a",
@ -54293,8 +54522,8 @@
}
],
"total_tokens": "250",
"withdrawn_tokens": "66.3216225325",
"remaining_tokens": "183.6783774675"
"withdrawn_tokens": "249.96123957",
"remaining_tokens": "0.03876043"
},
{
"address": "0xb7f112a2Ff7984Ef494E87b2539059d77d500f37",
@ -54307,6 +54536,12 @@
}
],
"withdrawals": [
{
"amount": "183.6407458275",
"user": "0xb7f112a2Ff7984Ef494E87b2539059d77d500f37",
"tranche_id": 6,
"tx": "0x66ecf2cabcc78741ac276ace894a5d203475284a7dbaa3df624dd9765b5e34f8"
},
{
"amount": "66.3197624125",
"user": "0xb7f112a2Ff7984Ef494E87b2539059d77d500f37",
@ -54315,8 +54550,8 @@
}
],
"total_tokens": "250",
"withdrawn_tokens": "66.3197624125",
"remaining_tokens": "183.6802375875"
"withdrawn_tokens": "249.96050824",
"remaining_tokens": "0.03949176"
},
{
"address": "0x967C162B564c83A30509CB77e572A98e1150a9D3",
@ -54329,6 +54564,12 @@
}
],
"withdrawals": [
{
"amount": "34.2757777525",
"user": "0x967C162B564c83A30509CB77e572A98e1150a9D3",
"tranche_id": 6,
"tx": "0x41d9be48e1130b253b786db21c3d6f8ab169114251b3a3866ce6e2d4ef1870e0"
},
{
"amount": "53.1604376525",
"user": "0x967C162B564c83A30509CB77e572A98e1150a9D3",
@ -54367,8 +54608,8 @@
}
],
"total_tokens": "250",
"withdrawn_tokens": "215.7242222475",
"remaining_tokens": "34.2757777525"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0xC9ce3264ac5d3Ff850744043c8F3f87899c1a1D2",
@ -54598,6 +54839,12 @@
}
],
"withdrawals": [
{
"amount": "157.270935085",
"user": "0x2740029049CF5d991202DeEd75965C6F34Aa36B9",
"tranche_id": 6,
"tx": "0xe613bfdfe6d2dbbff73e7089139458b2d905150649739392e067a652d3226557"
},
{
"amount": "92.729064915",
"user": "0x2740029049CF5d991202DeEd75965C6F34Aa36B9",
@ -54606,8 +54853,8 @@
}
],
"total_tokens": "250",
"withdrawn_tokens": "92.729064915",
"remaining_tokens": "157.270935085"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x573bd9385a35708C24478A385B2Cac38F2a247D3",
@ -55369,6 +55616,12 @@
}
],
"withdrawals": [
{
"amount": "25.40420228",
"user": "0xfeF4b56A370135A17Eef5ba8f5c308451a64Cf86",
"tranche_id": 6,
"tx": "0x6ecd654e1aeecdcc7073e1ec881b5b0fe4e24df9e9e226470aa35671e5c0be29"
},
{
"amount": "41.18821861",
"user": "0xfeF4b56A370135A17Eef5ba8f5c308451a64Cf86",
@ -55395,8 +55648,8 @@
}
],
"total_tokens": "250",
"withdrawn_tokens": "224.59579772",
"remaining_tokens": "25.40420228"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0xA8D108c6e91f16CEecDAC9d0230946D02D98d931",
@ -56906,10 +57159,17 @@
"tx": "0x057f65938b1360b6d2c4ebf5e789c67897cf60cb6a13f947004def03891afbd8"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0xe87aE24a4bE0158f25DFC6733c48177C86998B6F",
"tranche_id": 6,
"tx": "0x98625ddb4f277e915fd5478dc6f97b22ec9793a668bba209c9c82805da44f403"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x2735b9c217291c3FAa42FDF4bb130b504AAf4f9D",
@ -57768,6 +58028,12 @@
}
],
"withdrawals": [
{
"amount": "17.2834799106574",
"user": "0x34D4Da6a0829607fFAb8C0aFfDf6aBe059d89b86",
"tranche_id": 6,
"tx": "0x9d5a7c722b96c3a94457ca95d2152c3decdaf3ae89634a7503b0ff1e131d2960"
},
{
"amount": "21.9796154290108",
"user": "0x34D4Da6a0829607fFAb8C0aFfDf6aBe059d89b86",
@ -57776,8 +58042,8 @@
}
],
"total_tokens": "39.82",
"withdrawn_tokens": "21.9796154290108",
"remaining_tokens": "17.8403845709892"
"withdrawn_tokens": "39.2630953396682",
"remaining_tokens": "0.5569046603318"
},
{
"address": "0xd2FeED64D23115162723F1BBd97770190a1f4ffB",
@ -59852,10 +60118,17 @@
"tx": "0x716a7be06da5a7a3d8038907974887a31805ea8eeab5d130cba528e4d2094d9f"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0x7b82f39c7AC83442EE6b025D5B69b4A49E6FD168",
"tranche_id": 6,
"tx": "0xffcba1d72b13cb6fe9c5cf269b956cf55d54367d1dbb2d48ba6c03a2b94e9434"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x645c8D7Bc67b6c1E17d1dCA6621972ce50F3b373",
@ -60167,10 +60440,17 @@
"tx": "0x9f916cf09e8a3c4ade0ffce5190db464d0a2b1dadba78e1ee7ba5d6e751d6148"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0xc8aC50a6BA786450676f278Aa716Eb2bA7249B22",
"tranche_id": 6,
"tx": "0x91d6f0ea8c0c19d00a1c08a53fdf7e839a35fce2c3a80ce993f058c0b4ae9cac"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x92f15CBCE433FDcA0a87FD778e5BBbD0B3d7eE0f",
@ -60182,10 +60462,17 @@
"tx": "0x9f916cf09e8a3c4ade0ffce5190db464d0a2b1dadba78e1ee7ba5d6e751d6148"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "150",
"user": "0x92f15CBCE433FDcA0a87FD778e5BBbD0B3d7eE0f",
"tranche_id": 6,
"tx": "0x2638a870214b1d904c8e8e80cf35d4bc1b382801b9c16239af41ca2cf06a1654"
}
],
"total_tokens": "150",
"withdrawn_tokens": "0",
"remaining_tokens": "150"
"withdrawn_tokens": "150",
"remaining_tokens": "0"
},
{
"address": "0x0e80E23fa0ae15A06Ad986792F02625f80955E6E",
@ -60309,6 +60596,12 @@
}
],
"withdrawals": [
{
"amount": "46.069991352",
"user": "0x2814A08930732406a99139B6856001023239F9Dd",
"tranche_id": 6,
"tx": "0xa047a1c664e5c65286e3e4470d85497376315623a6be3c7f9289d1484d2209dc"
},
{
"amount": "18.472387566",
"user": "0x2814A08930732406a99139B6856001023239F9Dd",
@ -60323,8 +60616,8 @@
}
],
"total_tokens": "100",
"withdrawn_tokens": "53.930008648",
"remaining_tokens": "46.069991352"
"withdrawn_tokens": "100",
"remaining_tokens": "0"
},
{
"address": "0xdE6b94C3a927FF6c5b4E4C53556b155C6A7b1d94",
@ -60367,6 +60660,12 @@
}
],
"withdrawals": [
{
"amount": "23.0332214085",
"user": "0x8433BF349d4f71FB37c8CacF5D6D4863D43EaE23",
"tranche_id": 6,
"tx": "0xf55524a47a3b15876ef7965636d7caff9a0bb11566f359998bf4c79afad22673"
},
{
"amount": "26.9667785915",
"user": "0x8433BF349d4f71FB37c8CacF5D6D4863D43EaE23",
@ -60375,8 +60674,8 @@
}
],
"total_tokens": "50",
"withdrawn_tokens": "26.9667785915",
"remaining_tokens": "23.0332214085"
"withdrawn_tokens": "50",
"remaining_tokens": "0"
},
{
"address": "0xaD8a77d52Dd1E2dDD364AfE960DF49CAe1124C6D",
@ -61809,10 +62108,23 @@
"tx": "0xd23813c30e93f3867eaa257b7aef7052a050b1ee1c1a90102a3f40c5d989fe82"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "249.9983783575",
"user": "0xf0352681BcDBA8296b716d4EE085C27433Fff690",
"tranche_id": 6,
"tx": "0x65af2b3830875fac6eddb4ccdbaa62a6a43c9be071af5c4a201742704141e388"
},
{
"amount": "0.0016216425",
"user": "0xf0352681BcDBA8296b716d4EE085C27433Fff690",
"tranche_id": 6,
"tx": "0xbe02347c087aaed316855eca5a01116fc178087524e3e1885d20c0b119a2af2a"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x7D3e0aC6aB823fC1757B2B0F929c06BE155339Ae",
@ -62086,10 +62398,17 @@
"tx": "0xd23813c30e93f3867eaa257b7aef7052a050b1ee1c1a90102a3f40c5d989fe82"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "246.70865448",
"user": "0x00Ddf1483dEc8E5A38D0fFE78b6C14D0954D255e",
"tranche_id": 6,
"tx": "0xd1c9e882269eee55b067744c783ed78b875c7f3602bac5e8ed0e7c0b1fe623e6"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "246.70865448",
"remaining_tokens": "3.29134552"
},
{
"address": "0xe2A2F60ed248CDE54Fc2e3f5d4c00fDf3138CbBB",
@ -62676,6 +62995,12 @@
}
],
"withdrawals": [
{
"amount": "29.9837040875",
"user": "0xF047Ee0434548d5E2cA1F080ba6208A22C97d290",
"tranche_id": 6,
"tx": "0xe2885fb42ec26c0d8964c772f19eff356947add7b0b44f1cb7a3d49c0fdc830b"
},
{
"amount": "220.0162959125",
"user": "0xF047Ee0434548d5E2cA1F080ba6208A22C97d290",
@ -62684,8 +63009,8 @@
}
],
"total_tokens": "250",
"withdrawn_tokens": "220.0162959125",
"remaining_tokens": "29.9837040875"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0xd7DDc95Ac6FEe7069e913218545b7D6Fdfea0E05",
@ -62896,10 +63221,17 @@
"tx": "0xc8541da6a57f410b6faba47a5e5184bae700193b7bd042914fffc562114d92f5"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0x5417806fA87Be7F9bA7aEf4FFB9277E8922Fd9BE",
"tranche_id": 6,
"tx": "0x1c8ed1ce8127a594747ae20827659e12715af4dae9f855a8b641c1b35412b8a3"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x9b427a2EA3536FbFcD37E48c9EC798ea11E2Ec87",
@ -62911,10 +63243,17 @@
"tx": "0xc8541da6a57f410b6faba47a5e5184bae700193b7bd042914fffc562114d92f5"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0x9b427a2EA3536FbFcD37E48c9EC798ea11E2Ec87",
"tranche_id": 6,
"tx": "0xe5355d8976bf4d26018fea411fb0f01cbc1c43ebda8ad48970df81ae1ef7e8d6"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0xBD352479242D160FaEeFB21422786C5B201A1cEc",
@ -62926,10 +63265,17 @@
"tx": "0xc8541da6a57f410b6faba47a5e5184bae700193b7bd042914fffc562114d92f5"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0xBD352479242D160FaEeFB21422786C5B201A1cEc",
"tranche_id": 6,
"tx": "0x90a9ff32dac087d7f7a7050ee67a9fdf3fe2cc2dacf5f1e900dac53b9741fd68"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x1D92cb812FdeDF1a5aFE3c5080B2D3Ec102694c6",
@ -62956,10 +63302,17 @@
"tx": "0xc8541da6a57f410b6faba47a5e5184bae700193b7bd042914fffc562114d92f5"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0x02B5Fd5A74367D192b807Da32EaF1adF5E683573",
"tranche_id": 6,
"tx": "0xc833bf14e1891f661410ddafd4f97f009f63d4ac2ca906d19c74cf2223ff430a"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0xc008A0b1DF32d27A7283c199a7F2a92266029079",
@ -63655,6 +64008,12 @@
}
],
"withdrawals": [
{
"amount": "64.1735506975",
"user": "0xe8F33102aDD808E841268E4161326C76A3D31d24",
"tranche_id": 6,
"tx": "0xaf108d45b68929af7d63e571bf6f4d9d2415437c95470d70d70786398aa192ab"
},
{
"amount": "25.4253631225",
"user": "0xe8F33102aDD808E841268E4161326C76A3D31d24",
@ -63681,8 +64040,8 @@
}
],
"total_tokens": "250",
"withdrawn_tokens": "185.8264493025",
"remaining_tokens": "64.1735506975"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x57E427ad38383A4A97D0CC4c3Cc4bb9187ec3A00",
@ -63857,10 +64216,17 @@
"tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "120",
"user": "0xA301F00b655a45D454747B4b8fb3dcEFD8C4D66f",
"tranche_id": 6,
"tx": "0x7f3a81b45debc735d8c383ed438f92627b2f2a3021b92ffc3db4c6d8261f28f2"
}
],
"total_tokens": "120",
"withdrawn_tokens": "0",
"remaining_tokens": "120"
"withdrawn_tokens": "120",
"remaining_tokens": "0"
},
{
"address": "0x075A6AF774C9Ef7315879231730916ceD13e84f8",

View File

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

View File

@ -78,18 +78,51 @@
"tranche_id": 3,
"tranche_start": "2021-05-15T09:09:30.000Z",
"tranche_end": "2022-05-15T09:09:30.000Z",
"total_added": "10000",
"total_removed": "0",
"total_added": "10000.0000000000000005",
"total_removed": "5e-16",
"locked_amount": "0",
"deposits": [
{
"amount": "5e-16",
"user": "0x72c22822A19D20DE7e426fB84aa047399Ddd8853",
"tx": "0xe8f875e4c7e291676d4de38b40ac75cdb7ae23c3cbfc389152a67d25e557a632"
},
{
"amount": "10000",
"user": "0x1FF32F8A5895DAcdcC0Fe8bC6dDAa7D2c7717f3e",
"tx": "0x085994c06447bb8ed4b305a5fa5485e38e34b0347a40ee4a66233c8151fac0be"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "5e-16",
"user": "0x72c22822A19D20DE7e426fB84aa047399Ddd8853",
"tx": "0x454b4116649ee74c7fb6223ffe0d475128fdd8a5b9dca14a9ad3def605abd38c"
}
],
"users": [
{
"address": "0x72c22822A19D20DE7e426fB84aa047399Ddd8853",
"deposits": [
{
"amount": "5e-16",
"user": "0x72c22822A19D20DE7e426fB84aa047399Ddd8853",
"tranche_id": 3,
"tx": "0xe8f875e4c7e291676d4de38b40ac75cdb7ae23c3cbfc389152a67d25e557a632"
}
],
"withdrawals": [
{
"amount": "5e-16",
"user": "0x72c22822A19D20DE7e426fB84aa047399Ddd8853",
"tranche_id": 3,
"tx": "0x454b4116649ee74c7fb6223ffe0d475128fdd8a5b9dca14a9ad3def605abd38c"
}
],
"total_tokens": "5e-16",
"withdrawn_tokens": "5e-16",
"remaining_tokens": "0"
},
{
"address": "0x1FF32F8A5895DAcdcC0Fe8bC6dDAa7D2c7717f3e",
"deposits": [

View File

@ -1,7 +1,7 @@
export const Links = {
WALLET_RELEASES:
'https://github.com/vegaprotocol/vegawallet-desktop/releases',
WALLET_GUIDE: 'https://docs.vega.xyz/docs/mainnet/tools/vega-wallet',
WALLET_GUIDE: 'https://vega.xyz/wallet',
SUSHI_PAIRS: 'https://analytics.sushi.com/pairs/',
SUSHI_ONSEN_MENU: 'https://app.sushi.com/farm',
SUSHI_ONSEN_WHAT_IS:

View File

@ -9,6 +9,7 @@ export const generateDealTicketQuery = (
const defaultResult: DealTicketQuery = {
market: {
id: 'market-id',
name: 'ETHBTC Quarterly (30 Jun 2022)',
decimalPlaces: 2,
positionDecimalPlaces: 1,
state: MarketState.Active,

View File

@ -1,6 +1,6 @@
export default class BasePage {
closeDialogBtn = 'dialog-close';
porfolioUrl = '/portfolio';
portfolioUrl = '/portfolio';
marketsUrl = '/markets';
assetSelectField = 'select[name="asset"]';
toAddressField = 'input[name="to"]';
@ -10,21 +10,22 @@ export default class BasePage {
dialogText = 'dialog-text';
closeDialog() {
cy.getByTestId(this.closeDialogBtn, { timeout: 8000 }).click({
cy.getByTestId(this.closeDialogBtn, { timeout: 8000 })?.click({
force: true,
});
}
navigateToPortfolio() {
cy.get(`a[href='${this.porfolioUrl}']`).should('be.visible').click();
cy.get(`a[href='${this.portfolioUrl}']`)
.should('be.visible')
.click({ force: true });
cy.url().should('include', '/portfolio');
cy.getByTestId('portfolio');
}
navigateToMarkets() {
cy.get(`a[href='${this.marketsUrl}']`).should('be.visible').click();
cy.getByTestId('markets-link').should('be.visible').click({ force: true });
cy.url().should('include', '/markets');
cy.getByTestId('markets');
}
verifyFormErrorDisplayed(expectedError: string, expectedNumErrors: number) {
@ -35,7 +36,7 @@ export default class BasePage {
);
}
updateTransactionform(args?: {
updateTransactionForm(args?: {
asset?: string;
to?: string;
amount?: string;

View File

@ -1,13 +1,14 @@
import BasePage from './base-page';
export default class MarketPage extends BasePage {
marketRowHeaderClassname = '.ag-header-cell-text';
marketRowHeaderClassname = 'div > span.ag-header-cell-text';
marketRowNameColumn = 'tradableInstrument.instrument.code';
marketRowSymbolColumn =
'tradableInstrument.instrument.product.settlementAsset.symbol';
marketRowPrices = 'flash-cell';
marketRowDescription = 'name';
marketStateColId = 'data';
openMarketMenu = 'arrow-down';
validateMarketsAreDisplayed() {
// We need this to ensure that ag-grid is fully rendered before asserting
@ -27,16 +28,12 @@ export default class MarketPage extends BasePage {
'Description',
];
cy.get(this.marketRowHeaderClassname)
.each(($marketHeader, index) => {
cy.wrap($marketHeader).should(
'have.text',
expectedMarketHeaders[index]
);
})
.then(($list) => {
cy.wrap($list).should('have.length', expectedMarketHeaders.length);
});
for (let index = 0; index < expectedMarketHeaders.length; index++) {
cy.get(this.marketRowHeaderClassname).should(
'contain.text',
expectedMarketHeaders[index]
);
}
cy.get(`[col-id='${this.marketRowNameColumn}']`).each(($marketName) => {
cy.wrap($marketName).should('not.be.empty');
@ -65,4 +62,8 @@ export default class MarketPage extends BasePage {
'portfolio=orders&trade=orderbook'
);
}
clickOpenMarketMenu() {
cy.getByTestId(this.openMarketMenu).click();
}
}

View File

@ -2,8 +2,10 @@ import { Given } from 'cypress-cucumber-preprocessor/steps';
import { hasOperationName } from '..';
import { generateMarketList } from '../mocks/generate-market-list';
import BasePage from '../pages/base-page';
import MarketPage from '../pages/markets-page';
const basePage = new BasePage();
const marketPage = new MarketPage();
Given('I am on the homepage', () => {
cy.mockGQL('MarketsList', (req) => {
@ -15,4 +17,5 @@ Given('I am on the homepage', () => {
});
cy.visit('/');
basePage.closeDialog();
marketPage.validateMarketsAreDisplayed();
});

View File

@ -30,7 +30,7 @@ Then('I can see the deposit form', () => {
});
When('I submit a deposit with empty fields', () => {
depositsPage.updateTransactionform();
depositsPage.updateTransactionForm();
depositsPage.submitForm();
});
@ -39,7 +39,7 @@ Then('I can see empty form validation errors present', () => {
});
Then('I enter the following deposit details in deposit form', (table) => {
depositsPage.updateTransactionform({
depositsPage.updateTransactionForm({
asset: table.rowsHash().asset,
to: Cypress.env(table.rowsHash().to),
amount: table.rowsHash().amount,
@ -59,7 +59,7 @@ Then('Amount too small message shown', () => {
});
And('I enter a valid amount', () => {
depositsPage.updateTransactionform({ amount: '1' });
depositsPage.updateTransactionForm({ amount: '1' });
});
Then('Not approved message shown', () => {

View File

@ -19,6 +19,7 @@ const mockMarkets = () => {
Then('I navigate to markets page', () => {
mockMarkets();
marketsPage.navigateToMarkets();
marketsPage.clickOpenMarketMenu();
cy.wait('@Markets');
});

View File

@ -88,7 +88,7 @@ Given('I am on the trading page for an active market', () => {
cy.visit('/markets/market-id');
cy.wait('@Market');
cy.contains('Market: ACTIVE MARKET');
cy.contains('ACTIVE MARKET');
});
Given('I am on the trading page for a suspended market', () => {
@ -96,7 +96,7 @@ Given('I am on the trading page for a suspended market', () => {
cy.visit('/markets/market-id');
cy.wait('@Market');
cy.contains('Market: SUSPENDED MARKET');
cy.contains('SUSPENDED MARKET');
});
When('I click on {string} mocked market', (marketType) => {
@ -115,11 +115,11 @@ Then('trading page for {string} market is displayed', (marketType) => {
switch (marketType) {
case 'active':
cy.wait('@Market');
cy.contains('Market: ACTIVE MARKET');
cy.contains('ACTIVE MARKET');
break;
case 'suspended':
cy.wait('@Market');
cy.contains('Market: SUSPENDED MARKET');
cy.contains('SUSPENDED MARKET');
break;
}
tradingPage.clickOnTradesTab();

View File

@ -27,6 +27,7 @@ When('I connect to Vega Wallet', () => {
Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE')
);
vegaWallet.clickConnectVegaWallet();
vegaWallet.validateWalletConnected();
});
When('I open wallet dialog', () => {

View File

@ -1,13 +1,16 @@
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps';
import MarketPage from '../pages/markets-page';
import PortfolioPage from '../pages/portfolio-page';
import WithdrawalsPage from '../pages/withdrawals-page';
const marketPage = new MarketPage();
const portfolioPage = new PortfolioPage();
const withdrawalsPage = new WithdrawalsPage();
Given('I navigate to withdrawal page', () => {
cy.visit('/');
portfolioPage.closeDialog();
marketPage.validateMarketsAreDisplayed();
portfolioPage.navigateToPortfolio();
portfolioPage.navigateToWithdraw();
});
@ -26,14 +29,14 @@ When('click submit', () => {
});
When('I enter an invalid ethereum address', () => {
withdrawalsPage.updateTransactionform({
withdrawalsPage.updateTransactionForm({
to: '0x0dAAACaa868f87BB4666F918742141cAEAe893Fa',
});
withdrawalsPage.clickSubmit();
});
When('I select {string}', (selectedAsset) => {
withdrawalsPage.updateTransactionform({
withdrawalsPage.updateTransactionForm({
asset: selectedAsset,
});
});
@ -47,7 +50,7 @@ When('I click Use maximum', () => {
});
When('I enter the following details in withdrawal form', (table) => {
withdrawalsPage.updateTransactionform({
withdrawalsPage.updateTransactionForm({
asset: table.rowsHash().asset,
to: table.rowsHash().to,
amount: table.rowsHash().amount,
@ -56,7 +59,7 @@ When('I enter the following details in withdrawal form', (table) => {
});
When('I succesfully fill in and submit withdrawal form', () => {
withdrawalsPage.updateTransactionform({
withdrawalsPage.updateTransactionForm({
asset: Cypress.env('WITHDRAWAL_ASSET_ID'),
amount: '0.1',
});

View File

@ -20,11 +20,11 @@ export default class DealTicket {
);
if (isBuy == false) {
cy.getByTestId(this.sellOrder).click();
cy.getByTestId(this.sellOrder)?.click();
}
cy.getByTestId(this.orderSizeField).clear().type(orderSize);
cy.getByTestId(this.orderTypeDropDown).select(orderType);
cy.getByTestId(this.orderSizeField)?.clear().type(orderSize);
cy.getByTestId(this.orderTypeDropDown)?.select(orderType);
}
placeLimitOrder(
@ -33,10 +33,10 @@ export default class DealTicket {
orderPrice: string,
orderType: string
) {
cy.getByTestId(this.limitOrderType).click();
cy.getByTestId(this.limitOrderType)?.click();
if (isBuy == false) {
cy.getByTestId(this.sellOrder).click();
cy.getByTestId(this.sellOrder)?.click();
}
cy.getByTestId(this.orderSizeField).clear().type(orderSize);

View File

@ -50,6 +50,10 @@ export default class VegaWallet {
);
}
validateWalletConnected() {
cy.getByTestId(this.connectVegaBtn).should('contain.text', '…');
}
selectPublicKey() {
cy.getByTestId(this.selectPublicKeyBtn).click();
}

View File

@ -2,9 +2,38 @@ import { useRouter } from 'next/router';
import { Vega } from '../icons/vega';
import Link from 'next/link';
import { AnchorButton } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import { LocalStorage, t } from '@vegaprotocol/react-helpers';
import { useEffect, useState } from 'react';
export const Navbar = () => {
const initNavItemsState = [
{
name: t('Portfolio'),
path: '/portfolio',
testId: 'portfolio-link',
slug: '',
},
];
const [navItems, setNavItems] = useState(initNavItemsState);
const marketId = LocalStorage.getItem('marketId') ?? '';
useEffect(() => {
setNavItems([
{
name: t('Trading'),
path: '/markets',
testId: 'markets-link',
slug: marketId,
},
{
name: t('Portfolio'),
path: '/portfolio',
testId: 'portfolio-link',
slug: '',
},
]);
}, [marketId]);
return (
<nav className="flex items-center">
<Link href="/" passHref={true}>
@ -12,10 +41,7 @@ export const Navbar = () => {
<Vega className="fill-black dark:fill-white" />
</a>
</Link>
{[
{ name: t('Trading'), path: '/markets' },
{ name: t('Portfolio'), path: '/portfolio' },
].map((route) => (
{navItems.map((route) => (
<NavLink key={route.path} {...route} />
))}
</nav>
@ -26,20 +52,30 @@ interface NavLinkProps {
name: string;
path: string;
exact?: boolean;
testId?: string;
slug?: string;
}
const NavLink = ({ name, path, exact }: NavLinkProps) => {
const NavLink = ({
name,
path,
exact,
testId = name,
slug = '',
}: NavLinkProps) => {
const router = useRouter();
const isActive =
router.asPath === path || (!exact && router.asPath.startsWith(path));
const href = slug !== '' ? `${path}/${slug}` : path;
return (
<AnchorButton
variant={isActive ? 'accent' : 'inline'}
className="px-16 py-6 h-[38px] uppercase border-0 self-end xs:text-ui sm:text-body-large md:text-h5 lg:text-h4"
href={path}
data-testid={testId}
href={href}
onClick={(e) => {
e.preventDefault();
router.push(path);
router.push(href);
}}
>
{name}

View File

@ -1,4 +1,5 @@
import { gql, useQuery } from '@apollo/client';
import { LocalStorage } from '@vegaprotocol/react-helpers';
import { MarketTradingMode } from '@vegaprotocol/types';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import sortBy from 'lodash/sortBy';
@ -35,10 +36,13 @@ export function Index() {
// 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 setLandingDialog = useGlobalStore((state) => state.setLandingDialog);
const lastSelectedMarketId = LocalStorage.getItem('marketId');
useEffect(() => {
if (data) {
const marketId = marketList(data)[0]?.id;
const marketId = lastSelectedMarketId
? lastSelectedMarketId
: marketList(data)[0]?.id;
// If a default market is found, go to it with the landing dialog open
if (marketId) {
@ -50,7 +54,7 @@ export function Index() {
replace('/markets');
}
}
}, [data, replace, setLandingDialog]);
}, [data, lastSelectedMarketId, replace, setLandingDialog]);
return (
<AsyncRenderer data={data} loading={loading} error={error}>

View File

@ -1,21 +1,55 @@
import { gql } from '@apollo/client';
import type { Market, MarketVariables } from './__generated__/Market';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react';
import debounce from 'lodash/debounce';
import { PageQueryContainer } from '../../components/page-query-container';
import { TradeGrid, TradePanels } from './trade-grid';
import { t } from '@vegaprotocol/react-helpers';
import { LocalStorage, t } from '@vegaprotocol/react-helpers';
import { useGlobalStore } from '../../stores';
import { LandingDialog } from '@vegaprotocol/market-list';
import type { Market, MarketVariables } from './__generated__/Market';
import { Interval } from '@vegaprotocol/types';
// Top level page query
const MARKET_QUERY = gql`
query Market($marketId: ID!) {
query Market($marketId: ID!, $interval: Interval!, $since: String!) {
market(id: $marketId) {
id
name
tradingMode
state
decimalPlaces
data {
market {
id
}
markPrice
indicativeVolume
bestBidVolume
bestOfferVolume
bestStaticBidVolume
bestStaticOfferVolume
indicativeVolume
}
tradableInstrument {
instrument {
name
code
metadata {
tags
}
}
}
marketTimestamps {
open
close
}
candles(interval: $interval, since: $since) {
open
close
volume
}
}
}
`;
@ -29,6 +63,9 @@ const MarketPage = ({ id }: { id?: string }) => {
const marketId =
id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId);
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
const yTimestamp = new Date(yesterday * 1000).toISOString();
if (!marketId) {
return (
<Splash>
@ -37,12 +74,15 @@ const MarketPage = ({ id }: { id?: string }) => {
);
}
LocalStorage.setItem('marketId', marketId);
return (
<PageQueryContainer<Market, MarketVariables>
query={MARKET_QUERY}
options={{
variables: {
marketId,
interval: Interval.I1H,
since: yTimestamp,
},
fetchPolicy: 'network-only',
}}

View File

@ -3,10 +3,112 @@
// @generated
// This file was automatically generated and should not be edited.
import { Interval, MarketTradingMode, MarketState } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: Market
// ====================================================
export interface Market_market_data_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
}
export interface Market_market_data {
__typename: "MarketData";
/**
* market id of the associated mark price
*/
market: Market_market_data_market;
/**
* the mark price (actually an unsigned int)
*/
markPrice: string;
/**
* indicative volume if the auction ended now, 0 if not in auction mode
*/
indicativeVolume: string;
/**
* the aggregated volume being bid at the best bid price.
*/
bestBidVolume: string;
/**
* the aggregated volume being offered at the best offer price.
*/
bestOfferVolume: string;
/**
* the aggregated volume being offered at the best static bid price, excluding pegged orders
*/
bestStaticBidVolume: string;
/**
* the aggregated volume being offered at the best static offer price, excluding pegged orders.
*/
bestStaticOfferVolume: string;
}
export interface Market_market_tradableInstrument_instrument_metadata {
__typename: "InstrumentMetadata";
/**
* An arbitrary list of tags to associated to associate to the Instrument (string list)
*/
tags: string[] | null;
}
export interface Market_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* Full and fairly descriptive name for the instrument
*/
name: string;
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
*/
code: string;
/**
* Metadata for this instrument
*/
metadata: Market_market_tradableInstrument_instrument_metadata;
}
export interface Market_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: Market_market_tradableInstrument_instrument;
}
export interface Market_market_marketTimestamps {
__typename: "MarketTimestamps";
/**
* Time when the market is open and ready to accept trades
*/
open: string | null;
/**
* Time when the market is closed
*/
close: string | null;
}
export interface Market_market_candles {
__typename: "Candle";
/**
* Open price (uint64)
*/
open: string;
/**
* Close price (uint64)
*/
close: string;
/**
* Volume price (uint64)
*/
volume: string;
}
export interface Market_market {
__typename: "Market";
/**
@ -17,6 +119,47 @@ export interface Market_market {
* Market full name
*/
name: string;
/**
* Current mode of execution of the market
*/
tradingMode: MarketTradingMode;
/**
* Current state of the market
*/
state: MarketState;
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
* GBP 100 0 GBP 100
* GBP 100 2 GBP 1.00
* GBP 100 4 GBP 0.01
* GBP 1 4 GBP 0.0001 ( 0.01p )
*
* GBX (pence) 100 0 GBP 1.00 (100p )
* GBX (pence) 100 2 GBP 0.01 ( 1p )
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/
decimalPlaces: number;
/**
* marketData for the given market
*/
data: Market_market_data | null;
/**
* An instance of or reference to a tradable instrument.
*/
tradableInstrument: Market_market_tradableInstrument;
/**
* timestamps for state changes in the market
*/
marketTimestamps: Market_market_marketTimestamps;
/**
* Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by params
*/
candles: (Market_market_candles | null)[] | null;
}
export interface Market {
@ -28,4 +171,6 @@ export interface Market {
export interface MarketVariables {
marketId: string;
interval: Interval;
since: string;
}

View File

@ -1,8 +1,6 @@
import { MarketsContainer } from '@vegaprotocol/market-list';
const Markets = () => {
return <MarketsContainer />;
};
const Markets = () => <MarketsContainer />;
Markets.getInitialProps = () => ({
page: 'markets',

View File

@ -13,6 +13,9 @@ import { t } from '@vegaprotocol/react-helpers';
import { AccountsContainer } from '@vegaprotocol/accounts';
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import { SelectMarketDialog } from '@vegaprotocol/market-list';
import { ArrowDown, PriceCellChange } from '@vegaprotocol/ui-toolkit';
import type { CandleClose } from '@vegaprotocol/types';
const TradingViews = {
Candles: CandlesChartContainer,
@ -31,6 +34,58 @@ interface TradeGridProps {
market: Market_market;
}
export const TradeMarketHeader = ({ market }: TradeGridProps) => {
const [open, setOpen] = useState(false);
const candlesClose: string[] = (market?.candles || [])
.map((candle) => candle?.close)
.filter((c): c is CandleClose => c !== null);
const headerItemClassName = 'whitespace-nowrap flex flex-col';
const itemClassName =
'font-sans font-normal mb-0 text-dark/80 dark:text-white/80 text-ui-small';
const itemValueClassName =
'capitalize font-sans tracking-tighter text-black dark:text-white text-ui';
return (
<header className="w-full p-8">
<SelectMarketDialog dialogOpen={open} setDialogOpen={setOpen} />
<div className="flex flex-col md:flex-row gap-20 md:gap-64 ml-auto mr-8">
<button
onClick={() => setOpen(!open)}
className="shrink-0 dark:text-vega-yellow text-black text-h5 flex items-center gap-8 px-4 py-0 h-37 hover:bg-vega-yellow dark:hover:bg-white/20"
>
<span className="break-words text-left">{market.name}</span>
<ArrowDown color="yellow" borderX={8} borderTop={12} />
</button>
<div className="flex flex-auto items-start gap-64 overflow-x-scroll whitespace-nowrap w-[400px]">
<div className={headerItemClassName}>
<span className={itemClassName}>Change (24h)</span>
<PriceCellChange
candles={candlesClose}
decimalPlaces={market.decimalPlaces}
/>
</div>
<div className={headerItemClassName}>
<span className={itemClassName}>Volume</span>
<span className={itemValueClassName}>
{market.data && market.data.indicativeVolume !== '0'
? market.data.indicativeVolume
: '-'}
</span>
</div>
<div className={headerItemClassName}>
<span className={itemClassName}>Trading mode</span>
<span className={itemValueClassName}>{market.tradingMode}</span>
</div>
<div className={headerItemClassName}>
<span className={itemClassName}>State</span>
<span className={itemValueClassName}>{market.state}</span>
</div>
</div>
</div>
</header>
);
};
export const TradeGrid = ({ market }: TradeGridProps) => {
const wrapperClasses = classNames(
'h-full max-h-full',
@ -38,50 +93,49 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
'bg-black-10 dark:bg-white-10',
'text-ui'
);
return (
<div className={wrapperClasses}>
<header className="col-start-1 col-end-2 row-start-1 row-end-1 p-8">
<h1>
{t('Market')}: {market.name}
</h1>
</header>
<TradeGridChild className="col-start-1 col-end-2">
<GridTabs group="chart">
<GridTab id="candles" name={t('Candles')}>
<TradingViews.Candles marketId={market.id} />
</GridTab>
<GridTab id="depth" name={t('Depth')}>
<TradingViews.Depth marketId={market.id} />
</GridTab>
</GridTabs>
</TradeGridChild>
<TradeGridChild className="row-start-1 row-end-3">
<TradingViews.Ticket marketId={market.id} />
</TradeGridChild>
<TradeGridChild className="row-start-1 row-end-3">
<GridTabs group="trade">
<GridTab id="trades" name={t('Trades')}>
<TradingViews.Trades marketId={market.id} />
</GridTab>
<GridTab id="orderbook" name={t('Orderbook')}>
<TradingViews.Orderbook marketId={market.id} />
</GridTab>
</GridTabs>
</TradeGridChild>
<TradeGridChild className="col-span-3">
<GridTabs group="portfolio">
<GridTab id="orders" name={t('Orders')}>
<TradingViews.Orders />
</GridTab>
<GridTab id="positions" name={t('Positions')}>
<TradingViews.Positions />
</GridTab>
<GridTab id="accounts" name={t('Accounts')}>
<TradingViews.Accounts />
</GridTab>
</GridTabs>
</TradeGridChild>
</div>
<>
<TradeMarketHeader market={market} />
<div className={wrapperClasses}>
<TradeGridChild className="row-start-1 row-end-3">
<GridTabs group="chart">
<GridTab id="candles" name={t('Candles')}>
<TradingViews.Candles marketId={market.id} />
</GridTab>
<GridTab id="depth" name={t('Depth')}>
<TradingViews.Depth marketId={market.id} />
</GridTab>
</GridTabs>
</TradeGridChild>
<TradeGridChild className="row-start-1 row-end-3">
<TradingViews.Ticket marketId={market.id} />
</TradeGridChild>
<TradeGridChild className="row-start-1 row-end-3">
<GridTabs group="trade">
<GridTab id="trades" name={t('Trades')}>
<TradingViews.Trades marketId={market.id} />
</GridTab>
<GridTab id="orderbook" name={t('Orderbook')}>
<TradingViews.Orderbook marketId={market.id} />
</GridTab>
</GridTabs>
</TradeGridChild>
<TradeGridChild className="col-span-3">
<GridTabs group="portfolio">
<GridTab id="orders" name={t('Orders')}>
<TradingViews.Orders />
</GridTab>
<GridTab id="positions" name={t('Positions')}>
<TradingViews.Positions />
</GridTab>
<GridTab id="accounts" name={t('Accounts')}>
<TradingViews.Accounts />
</GridTab>
</GridTabs>
</TradeGridChild>
</div>
</>
);
};
@ -124,11 +178,7 @@ export const TradePanels = ({ market }: TradePanelsProps) => {
return (
<div className="h-full grid grid-rows-[min-content_1fr_min-content]">
<header className="p-8">
<h1>
{t('Market')}: {market.name}
</h1>
</header>
<TradeMarketHeader market={market} />
<div className="h-full">
<AutoSizer>
{({ width, height }) => (

View File

@ -55,6 +55,10 @@ export interface DealTicketQuery_market {
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
/**
* 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)

View File

@ -1,5 +1,5 @@
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
import { gql, useQuery } from '@apollo/client';
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
import { DealTicketManager } from './deal-ticket-manager';
import type {
DealTicketQuery,
@ -11,6 +11,7 @@ const DEAL_TICKET_QUERY = gql`
query DealTicketQuery($marketId: ID!) {
market(id: $marketId) {
id
name
decimalPlaces
positionDecimalPlaces
state

View File

@ -1,2 +1,3 @@
export * from './landing-dialog';
export * from './select-market-dialog';
export * from './select-market-list';

View File

@ -0,0 +1,33 @@
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { SelectMarketDialog } from './select-market-dialog';
import { MockedProvider } from '@apollo/client/testing';
jest.mock(
'next/link',
() =>
({ children }: { children: ReactNode }) =>
children
);
jest.mock('next/router', () => ({
useRouter() {
return {
route: '/',
pathname: '',
query: '',
asPath: '',
};
},
}));
describe('SelectMarketDialog', () => {
it('should render select a market dialog', () => {
render(
<MockedProvider>
<SelectMarketDialog dialogOpen={true} setDialogOpen={() => jest.fn()} />
</MockedProvider>
);
expect(screen.getByText('Select a market')).toBeTruthy();
});
});

View File

@ -0,0 +1,28 @@
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import { MarketsContainer } from '../markets-container';
export interface SelectMarketListProps {
dialogOpen: boolean;
setDialogOpen: (open: boolean) => void;
}
export const SelectMarketDialog = ({
dialogOpen,
setDialogOpen,
}: SelectMarketListProps) => {
return (
<Dialog
title={t('Select a market')}
intent={Intent.Prompt}
open={dialogOpen}
onChange={() => setDialogOpen(false)}
titleClassNames="font-bold font-sans text-3xl tracking-tight mb-0 pl-8"
contentClassNames="w-full md:w-[1120px]"
>
<div className="h-[200px] w-full">
<MarketsContainer />
</div>
</Dialog>
);
};

View File

@ -3,19 +3,21 @@ import {
PriceCell,
t,
} from '@vegaprotocol/react-helpers';
import type { CandleClose } from '@vegaprotocol/types';
import { PriceCellChange, Sparkline } from '@vegaprotocol/ui-toolkit';
import Link from 'next/link';
import { mapDataToMarketList } from '../../utils';
import type { MarketList } from '../markets-container/__generated__/MarketList';
export interface SelectMarketListProps {
export interface SelectMarketListDataProps {
data: MarketList | undefined;
onSelect: (id: string) => void;
}
type CandleClose = Required<string>;
export const SelectMarketList = ({ data, onSelect }: SelectMarketListProps) => {
export const SelectMarketList = ({
data,
onSelect,
}: SelectMarketListDataProps) => {
const thClassNames = (direction: 'left' | 'right') =>
`px-8 text-${direction} font-sans font-normal text-ui-small leading-9 mb-0 text-dark/80 dark:text-white/80`;
const tdClassNames =

View File

@ -13,7 +13,7 @@ import type {
import { marketsDataProvider } from './markets-data-provider';
export const MarketsContainer = () => {
const { pathname, push } = useRouter();
const { push } = useRouter();
const gridRef = useRef<AgGridReact | null>(null);
const update = useCallback(
(delta: Markets_markets_data) => {
@ -57,7 +57,7 @@ export const MarketsContainer = () => {
ref={gridRef}
data={data}
onRowClicked={(id) =>
push(`${pathname}/${id}?portfolio=orders&trade=orderbook`)
push(`/markets/${id}?portfolio=orders&trade=orderbook`)
}
/>
</AsyncRenderer>

View File

@ -4,7 +4,7 @@ import type { Positions_party_positions } from './__generated__/Positions';
import { MarketTradingMode } from '@vegaprotocol/types';
const singleRow: Positions_party_positions = {
realisedPNL: '5',
realisedPNL: '520000000',
openVolume: '100',
unrealisedPNL: '895000',
averageEntryPrice: '1129935',
@ -93,7 +93,7 @@ it('Correct formatting applied', async () => {
'+100',
'11.29935',
'11.38885',
'+5',
'+5,200.000',
];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);

View File

@ -131,8 +131,10 @@ export const PositionsTable = forwardRef<AgGridReact, PositionsTableProps>(
'color-vega-red': ({ value }: { value: string }) =>
Number(value) < 0,
}}
valueFormatter={({ value }: ValueFormatterParams) =>
volumePrefix(value)
valueFormatter={({ value, data }: ValueFormatterParams) =>
volumePrefix(
addDecimalsFormatNumber(value, data.market.decimalPlaces, 3)
)
}
cellRenderer="PriceFlashCell"
/>

View File

@ -46,8 +46,8 @@ interface GetDelta<SubscriptionData, Delta> {
}
/**
* @param subscriptionQuery query that will beused for subscription
* @param update function that will be execued on each onNext, it should update data base on delta, it can restart data provider
* @param subscriptionQuery query that will be used for subscription
* @param update function that will be executed on each onNext, it should update data base on delta, it can restart data provider
* @param getData transforms received query data to format that will be stored in data provider
* @param getDelta transforms delta data to format that will be stored in data provider
* @param fetchPolicy
@ -63,7 +63,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
): Subscribe<Data, Delta> {
// list of callbacks passed through subscribe call
const callbacks: UpdateCallback<Data, Delta>[] = [];
// subscription is started before inital query, all deltas that will arrive before inital query response are put on queue
// subscription is started before initial query, all deltas that will arrive before initial query response are put on queue
const updateQueue: Delta[] = [];
let variables: OperationVariables | undefined = undefined;
@ -88,7 +88,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
callbacks.forEach((callback) => notify(callback, delta));
};
const initalFetch = async () => {
const initialFetch = async () => {
if (!client) {
return;
}
@ -99,7 +99,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
fetchPolicy,
});
data = getData(res.data);
// if there was some updates received from subscription during initial query loading apply them on just reveived data
// if there was some updates received from subscription during initial query loading apply them on just received data
if (data && updateQueue && updateQueue.length > 0) {
data = produce(data, (draft) => {
while (updateQueue.length) {
@ -135,7 +135,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
} else {
loading = true;
error = undefined;
initalFetch();
initialFetch();
}
};
@ -176,7 +176,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
},
() => restart()
);
await initalFetch();
await initialFetch();
};
const reset = () => {
@ -242,8 +242,8 @@ const memoize = <Data, Delta>(
/**
* @param query Query<QueryData>
* @param subscriptionQuery Query<SubscriptionData> query that will beused for subscription
* @param update Update<Data, Delta> function that will be execued on each onNext, it should update data base on delta, it can restart data provider
* @param subscriptionQuery Query<SubscriptionData> query that will be used for subscription
* @param update Update<Data, Delta> function that will be executed on each onNext, it should update data base on delta, it can restart data provider
* @param getData transforms received query data to format that will be stored in data provider
* @param getDelta transforms delta data to format that will be stored in data provider
* @param fetchPolicy

View File

@ -13,7 +13,7 @@ export const PriceCell = React.memo(
return <span data-testid="price">-</span>;
}
return (
<span className="font-mono relative" data-testid="price">
<span className="font-mono relative text-ui-small" data-testid="price">
{valueFormatted}
</span>
);

1
libs/types/src/candle.ts Normal file
View File

@ -0,0 +1 @@
export type CandleClose = Required<string>;

View File

@ -1 +1,2 @@
export * from './__generated__/globalTypes';
export * from './candle';

View File

@ -1,13 +1,38 @@
export const ArrowUp = () => (
export interface ArrowStyleProps {
color?: string;
borderX?: number;
borderTop?: number;
borderBottom?: number;
}
export const ArrowUp = ({
color = 'green',
borderX = 4,
borderBottom = 4,
}: ArrowStyleProps) => (
<span
data-testid="arrow-up"
className="w-0 h-0 border-x border-x-[4px] border-solid border-x-transparent border-b-[4px] border-b-green-dark dark:border-b-green"
style={{
borderLeft: `${borderX}px solid transparent`,
borderRight: `${borderX}px solid transparent`,
borderBottom: `${borderBottom}px solid`,
}}
className={`w-0 h-0 border-b-${color}-dark dark:border-b-${color}`}
></span>
);
export const ArrowDown = () => (
export const ArrowDown = ({
color = 'red',
borderX = 4,
borderTop = 4,
}: ArrowStyleProps) => (
<span
data-testid="arrow-down"
className="w-0 h-0 border-x border-x-[4px] border-solid border-x-transparent border-t-[4px] border-t-red-dark dark:border-t-red"
style={{
borderLeft: `${borderX}px solid transparent`,
borderRight: `${borderX}px solid transparent`,
borderTop: `${borderTop}px solid`,
}}
className={`w-0 h-0 border-t-${color}-dark dark:border-t-${color}`}
></span>
);

View File

@ -215,12 +215,12 @@ export const AnchorButton = forwardRef<HTMLAnchorElement, AnchorButtonProps>(
className,
prependIconName,
appendIconName,
...prosp
...props
},
ref
) => {
return (
<a ref={ref} className={classes(className, variant)} {...prosp}>
<a ref={ref} className={classes(className, variant)} {...props}>
{getContent(children, prependIconName, appendIconName)}
</a>
);

View File

@ -12,6 +12,7 @@ interface DialogProps {
title?: string;
intent?: Intent;
titleClassNames?: string;
contentClassNames?: string;
}
export function Dialog({
@ -21,13 +22,15 @@ export function Dialog({
title,
intent,
titleClassNames,
contentClassNames,
}: DialogProps) {
const contentClasses = classNames(
// Positions the modal in the center of screen
'z-20 fixed w-full md:w-[520px] px-28 py-24 top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]',
// Need to apply background and text colors again as content is rendered in a portal
'dark:bg-black dark:text-white-95 bg-white text-black-95',
getIntentShadow(intent)
getIntentShadow(intent),
contentClassNames
);
return (
<DialogPrimitives.Root open={open} onOpenChange={(x) => onChange(x)}>

View File

@ -60,6 +60,7 @@
"react-dom": "17.0.2",
"react-hook-form": "^7.27.0",
"react-i18next": "^11.11.4",
"react-intersection-observer": "^9.2.2",
"react-router-dom": "^6.3.0",
"react-syntax-highlighter": "^15.4.5",
"react-use-websocket": "^3.0.0",

View File

@ -18049,6 +18049,11 @@ react-inspector@^5.1.0:
is-dom "^1.0.0"
prop-types "^15.0.0"
react-intersection-observer@^9.2.2:
version "9.2.2"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.2.2.tgz#4f72487e2e9c62b6a6f55f38c5b0b6b7dc6c196d"
integrity sha512-xrFOWo5DbDzayAuDn6WpLrXfw73mvfx4WjuAjjy7dY7jWeTTle5+18nGtvMlR7q0npJanmmh6/CaSaSg4JxTJQ==
react-is@17.0.2, react-is@^17.0.1, react-is@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"