Feat/63 Deal ticket (#82)
* scaffold dealticket package, remove trading views from react-helpers * add deal ticket component, add intent utils, expand dialog and form group styles * add splash component, show market not found message if market doesnt exist * tidy up error handling * add handleError method for vega tx hook * add better testname for provider test, flesh out tests a bit more for deal ticket * Add unit tests for useVegaTransaction and useOrderSubmit hooks * add wrapper component for order dialog styles * add vega styled loader to ui toolkit and use in order dialog * add title prop to order dialog * split limit and market tickets into own files * add button radio component * revert dialog styles * move splash component to ui-toolkit, add story * convert intent to enum * Make button always type=button unless type prop is passed * inline filter logic for tif selector * add date-fns, add datetime to helpers * add order types to wallet package, make price undefined if order type is market * use enums in deal ticket logic * tidy up order state by moving submit and transaction hooks out of deal ticket * add comment for dialog styles * remove decimal from price input * add types package, delete old generated types from trading project * rename types package to graphql * update generate command to point to correct locations * fix use order submit test * use intent shadow helper * remove date-fns and format manually, update submit button error to use input-error * remove stray console.log
This commit is contained in:
parent
7b4437f407
commit
313e6e1217
12
apps/trading/__generated__/globalTypes.ts
generated
12
apps/trading/__generated__/globalTypes.ts
generated
@ -1,12 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
//==============================================================
|
||||
// START Enums and Input Objects
|
||||
//==============================================================
|
||||
|
||||
//==============================================================
|
||||
// END Enums and Input Objects
|
||||
//==============================================================
|
@ -4,6 +4,6 @@ module.exports = {
|
||||
name: 'vega',
|
||||
url: process.env.NX_VEGA_URL,
|
||||
},
|
||||
includes: ['{components,lib,pages}/**/*.{ts,tsx,js,jsx,graphql}'],
|
||||
includes: ['{components,lib,pages,hooks}/**/*.{ts,tsx,js,jsx,graphql}'],
|
||||
},
|
||||
};
|
||||
|
@ -0,0 +1,74 @@
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { DealTicket } from '@vegaprotocol/deal-ticket';
|
||||
import { OrderStatus } from '@vegaprotocol/graphql';
|
||||
import { useOrderSubmit } from '../../hooks/use-order-submit';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { VegaTxStatus } from '../../hooks/use-vega-transaction';
|
||||
import { OrderDialog } from './order-dialog';
|
||||
|
||||
export const DealTicketContainer = ({ market }) => {
|
||||
const [orderDialogOpen, setOrderDialogOpen] = useState(false);
|
||||
const { submit, transaction, finalizedOrder, reset } = useOrderSubmit(market);
|
||||
|
||||
const getDialogIntent = (status: VegaTxStatus) => {
|
||||
if (finalizedOrder) {
|
||||
if (
|
||||
finalizedOrder.status === OrderStatus.Active ||
|
||||
finalizedOrder.status === OrderStatus.Filled ||
|
||||
finalizedOrder.status === OrderStatus.PartiallyFilled
|
||||
) {
|
||||
return Intent.Success;
|
||||
}
|
||||
|
||||
if (finalizedOrder.status === OrderStatus.Parked) {
|
||||
return Intent.Warning;
|
||||
}
|
||||
|
||||
return Intent.Danger;
|
||||
}
|
||||
|
||||
if (status === VegaTxStatus.Rejected) {
|
||||
return Intent.Danger;
|
||||
}
|
||||
|
||||
return Intent.Progress;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (transaction.status !== VegaTxStatus.Default) {
|
||||
setOrderDialogOpen(true);
|
||||
}
|
||||
}, [transaction.status]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DealTicket
|
||||
market={market}
|
||||
submit={submit}
|
||||
transactionStatus={
|
||||
transaction.status === VegaTxStatus.AwaitingConfirmation ||
|
||||
transaction.status === VegaTxStatus.Pending
|
||||
? 'pending'
|
||||
: 'default'
|
||||
}
|
||||
/>
|
||||
<Dialog
|
||||
open={orderDialogOpen}
|
||||
onChange={(isOpen) => {
|
||||
setOrderDialogOpen(isOpen);
|
||||
|
||||
// If closing reset
|
||||
if (!isOpen) {
|
||||
reset();
|
||||
}
|
||||
}}
|
||||
intent={getDialogIntent(transaction.status)}
|
||||
>
|
||||
<OrderDialog
|
||||
transaction={transaction}
|
||||
finalizedOrder={finalizedOrder}
|
||||
/>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
1
apps/trading/components/deal-ticket-container/index.ts
Normal file
1
apps/trading/components/deal-ticket-container/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './deal-ticket-container';
|
@ -0,0 +1,95 @@
|
||||
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import { ReactNode } from 'react';
|
||||
import {
|
||||
TransactionState,
|
||||
VegaTxStatus,
|
||||
} from '../../hooks/use-vega-transaction';
|
||||
import { OrderEvent_busEvents_event_Order } from '@vegaprotocol/graphql';
|
||||
|
||||
interface OrderDialogProps {
|
||||
transaction: TransactionState;
|
||||
finalizedOrder: OrderEvent_busEvents_event_Order | null;
|
||||
}
|
||||
|
||||
export const OrderDialog = ({
|
||||
transaction,
|
||||
finalizedOrder,
|
||||
}: OrderDialogProps) => {
|
||||
// TODO: When wallets support confirming transactions return UI for 'awaiting confirmation' step
|
||||
|
||||
// Rejected by wallet
|
||||
if (transaction.status === VegaTxStatus.Rejected) {
|
||||
return (
|
||||
<OrderDialogWrapper
|
||||
title="Order rejected by wallet"
|
||||
icon={<Icon name="warning-sign" size={20} />}
|
||||
>
|
||||
{transaction.error && (
|
||||
<pre className="text-ui break-all whitespace-pre-wrap">
|
||||
{JSON.stringify(transaction.error, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
</OrderDialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Pending consensus
|
||||
if (!finalizedOrder) {
|
||||
return (
|
||||
<OrderDialogWrapper
|
||||
title="Awaiting network confirmation"
|
||||
icon={<Loader />}
|
||||
>
|
||||
{transaction.hash && (
|
||||
<p className="break-all">Tx hash: {transaction.hash}</p>
|
||||
)}
|
||||
</OrderDialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Order on network but was rejected
|
||||
if (finalizedOrder.status === 'Rejected') {
|
||||
return (
|
||||
<OrderDialogWrapper
|
||||
title="Order failed"
|
||||
icon={<Icon name="warning-sign" size={20} />}
|
||||
>
|
||||
<p>Reason: {finalizedOrder.rejectionReason}</p>
|
||||
</OrderDialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OrderDialogWrapper
|
||||
title="Order placed"
|
||||
icon={<Icon name="tick" size={20} />}
|
||||
>
|
||||
<p>Status: {finalizedOrder.status}</p>
|
||||
<p>Market: {finalizedOrder.market.name}</p>
|
||||
<p>Amount: {finalizedOrder.size}</p>
|
||||
{finalizedOrder.type === 'Limit' && <p>Price: {finalizedOrder.price}</p>}
|
||||
</OrderDialogWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
interface OrderDialogWrapperProps {
|
||||
children: ReactNode;
|
||||
icon: ReactNode;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const OrderDialogWrapper = ({
|
||||
children,
|
||||
icon,
|
||||
title,
|
||||
}: OrderDialogWrapperProps) => {
|
||||
return (
|
||||
<div className="flex gap-12 max-w-full">
|
||||
<div className="pt-8 fill-current">{icon}</div>
|
||||
<div className="flex-1">
|
||||
<h1 className="text-h4">{title}</h1>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import { OperationVariables, QueryHookOptions, useQuery } from '@apollo/client';
|
||||
import classNames from 'classnames';
|
||||
import { DocumentNode } from 'graphql';
|
||||
import { ReactNode } from 'react';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface PageQueryContainerProps<TData, TVariables> {
|
||||
query: DocumentNode;
|
||||
@ -15,19 +15,13 @@ export const PageQueryContainer = <TData, TVariables = OperationVariables>({
|
||||
children,
|
||||
}: PageQueryContainerProps<TData, TVariables>) => {
|
||||
const { data, loading, error } = useQuery<TData, TVariables>(query, options);
|
||||
const splashClasses = classNames(
|
||||
'w-full h-full',
|
||||
'flex items-center justify-center'
|
||||
);
|
||||
|
||||
if (loading || !data) {
|
||||
return <div className={splashClasses}>Loading...</div>;
|
||||
return <Splash>Loading...</Splash>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={splashClasses}>Something went wrong: {error.message}</div>
|
||||
);
|
||||
return <Splash>Something went wrong: {error.message}</Splash>;
|
||||
}
|
||||
|
||||
return <>{children(data)}</>;
|
||||
|
153
apps/trading/hooks/use-order-submit.spec.tsx
Normal file
153
apps/trading/hooks/use-order-submit.spec.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { Order } from '@vegaprotocol/deal-ticket';
|
||||
import {
|
||||
VegaKeyExtended,
|
||||
VegaWalletContext,
|
||||
VegaWalletContextShape,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import { ReactNode } from 'react';
|
||||
import { useOrderSubmit } from './use-order-submit';
|
||||
import { VegaTxStatus } from './use-vega-transaction';
|
||||
|
||||
const defaultWalletContext = {
|
||||
keypair: null,
|
||||
keypairs: [],
|
||||
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
|
||||
connect: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
selectPublicKey: jest.fn(),
|
||||
connector: null,
|
||||
};
|
||||
|
||||
function setup(
|
||||
context?: Partial<VegaWalletContextShape>,
|
||||
market = { id: 'market-id', decimalPlaces: 2 }
|
||||
) {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ ...defaultWalletContext, ...context }}
|
||||
>
|
||||
{children}
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
);
|
||||
return renderHook(() => useOrderSubmit(market), { wrapper });
|
||||
}
|
||||
|
||||
test('Has the correct default state', () => {
|
||||
const { result } = setup();
|
||||
expect(typeof result.current.submit).toEqual('function');
|
||||
expect(typeof result.current.reset).toEqual('function');
|
||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
|
||||
expect(result.current.transaction.hash).toEqual(null);
|
||||
expect(result.current.transaction.error).toEqual(null);
|
||||
});
|
||||
|
||||
test('Should not sendTx if no keypair', async () => {
|
||||
const mockSendTx = jest.fn();
|
||||
const { result } = setup({ sendTx: mockSendTx, keypairs: [], keypair: null });
|
||||
await act(async () => {
|
||||
result.current.submit({} as Order);
|
||||
});
|
||||
expect(mockSendTx).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Should not sendTx side is not specified', async () => {
|
||||
const mockSendTx = jest.fn();
|
||||
const keypair = {
|
||||
pub: '0x123',
|
||||
} as VegaKeyExtended;
|
||||
const { result } = setup({
|
||||
sendTx: mockSendTx,
|
||||
keypairs: [keypair],
|
||||
keypair,
|
||||
});
|
||||
await act(async () => {
|
||||
result.current.submit({} as Order);
|
||||
});
|
||||
expect(mockSendTx).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Create an Id if a signature is returned', async () => {
|
||||
const signature =
|
||||
'597a7706491e6523c091bab1e4d655b62c45a224e80f6cd92ac366aa5dd9a070cc7dd3c6919cb07b81334b876c662dd43bdbe5e827c8baa17a089feb654fab0b';
|
||||
const expectedId =
|
||||
'2FE09B0E2E6ED35F8883802629C7D609D3CC2FC9CE3CEC0B7824A0D581BD3747';
|
||||
const successObj = {
|
||||
tx: {
|
||||
inputData: 'input-data',
|
||||
signature: {
|
||||
algo: 'algo',
|
||||
version: 1,
|
||||
value: signature,
|
||||
},
|
||||
},
|
||||
txHash: '0x123',
|
||||
};
|
||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(successObj));
|
||||
const keypair = {
|
||||
pub: '0x123',
|
||||
} as VegaKeyExtended;
|
||||
const { result } = setup({
|
||||
sendTx: mockSendTx,
|
||||
keypairs: [keypair],
|
||||
keypair,
|
||||
});
|
||||
await act(async () => {
|
||||
result.current.submit({
|
||||
type: OrderType.Market,
|
||||
side: OrderSide.Buy,
|
||||
size: '1',
|
||||
timeInForce: OrderTimeInForce.FOK,
|
||||
});
|
||||
});
|
||||
expect(result.current.id).toEqual(expectedId);
|
||||
});
|
||||
|
||||
test('Should submit a correctly formatted order', async () => {
|
||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve({}));
|
||||
const keypair = {
|
||||
pub: '0x123',
|
||||
} as VegaKeyExtended;
|
||||
const market = {
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
};
|
||||
const { result } = setup(
|
||||
{
|
||||
sendTx: mockSendTx,
|
||||
keypairs: [keypair],
|
||||
keypair,
|
||||
},
|
||||
market
|
||||
);
|
||||
|
||||
const order = {
|
||||
type: OrderType.Limit,
|
||||
size: '10',
|
||||
timeInForce: OrderTimeInForce.GTT,
|
||||
side: OrderSide.Buy,
|
||||
price: '1234567.89',
|
||||
expiration: new Date('2022-01-01'),
|
||||
};
|
||||
await act(async () => {
|
||||
result.current.submit(order);
|
||||
});
|
||||
|
||||
expect(mockSendTx).toHaveBeenCalledWith({
|
||||
pubKey: keypair.pub,
|
||||
propagate: true,
|
||||
orderSubmission: {
|
||||
type: OrderType.Limit,
|
||||
marketId: market.id, // Market provided from hook arugment
|
||||
size: '10',
|
||||
side: OrderSide.Buy,
|
||||
timeInForce: OrderTimeInForce.GTT,
|
||||
price: '123456789', // Decimal removed
|
||||
expiresAt: order.expiration.getTime() + '000000', // Nanoseconds appened
|
||||
},
|
||||
});
|
||||
});
|
157
apps/trading/hooks/use-order-submit.ts
Normal file
157
apps/trading/hooks/use-order-submit.ts
Normal file
@ -0,0 +1,157 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { gql, useSubscription } from '@apollo/client';
|
||||
import { ethers } from 'ethers';
|
||||
import { SHA3 } from 'sha3';
|
||||
import { Order } from '@vegaprotocol/deal-ticket';
|
||||
import { OrderType, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVegaTransaction } from './use-vega-transaction';
|
||||
import {
|
||||
OrderEvent,
|
||||
OrderEventVariables,
|
||||
OrderEvent_busEvents_event_Order,
|
||||
} from '@vegaprotocol/graphql';
|
||||
import { removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const ORDER_EVENT_SUB = gql`
|
||||
subscription OrderEvent($partyId: ID!) {
|
||||
busEvents(partyId: $partyId, batchSize: 0, types: [Order]) {
|
||||
eventId
|
||||
block
|
||||
type
|
||||
event {
|
||||
... on Order {
|
||||
type
|
||||
id
|
||||
status
|
||||
rejectionReason
|
||||
createdAt
|
||||
size
|
||||
price
|
||||
market {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface UseOrderSubmitMarket {
|
||||
id: string;
|
||||
decimalPlaces: number;
|
||||
}
|
||||
|
||||
export const useOrderSubmit = (market: UseOrderSubmitMarket) => {
|
||||
const { keypair } = useVegaWallet();
|
||||
const { send, transaction, reset: resetTransaction } = useVegaTransaction();
|
||||
const [id, setId] = useState('');
|
||||
const [finalizedOrder, setFinalizedOrder] =
|
||||
useState<OrderEvent_busEvents_event_Order | null>(null);
|
||||
|
||||
// Start a subscription looking for the newly created order
|
||||
useSubscription<OrderEvent, OrderEventVariables>(ORDER_EVENT_SUB, {
|
||||
variables: { partyId: keypair?.pub || '' },
|
||||
skip: !id,
|
||||
onSubscriptionData: ({ subscriptionData }) => {
|
||||
if (!subscriptionData.data.busEvents.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No types available for the subscription result
|
||||
const matchingOrderEvent = subscriptionData.data.busEvents.find((e) => {
|
||||
if (e.event.__typename !== 'Order') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.event.id === id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (
|
||||
matchingOrderEvent &&
|
||||
matchingOrderEvent.event.__typename === 'Order'
|
||||
) {
|
||||
setFinalizedOrder(matchingOrderEvent.event);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (finalizedOrder) {
|
||||
resetTransaction();
|
||||
}
|
||||
}, [finalizedOrder, resetTransaction]);
|
||||
|
||||
const submit = useCallback(
|
||||
async (order: Order) => {
|
||||
if (!keypair || !order.side) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFinalizedOrder(null);
|
||||
|
||||
const res = await send({
|
||||
pubKey: keypair.pub,
|
||||
propagate: true,
|
||||
orderSubmission: {
|
||||
marketId: market.id,
|
||||
price:
|
||||
order.type === OrderType.Market
|
||||
? undefined
|
||||
: removeDecimal(order.price, market.decimalPlaces),
|
||||
size: order.size,
|
||||
type: order.type,
|
||||
side: order.side,
|
||||
timeInForce: order.timeInForce,
|
||||
expiresAt: order.expiration
|
||||
? // Wallet expects timestamp in nanoseconds, we don't have that level of accuracy so
|
||||
// just append 6 zeroes
|
||||
order.expiration.getTime().toString() + '000000'
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
if (res?.signature) {
|
||||
setId(determineId(res.signature).toUpperCase());
|
||||
}
|
||||
},
|
||||
[market, keypair, send]
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
resetTransaction();
|
||||
setFinalizedOrder(null);
|
||||
setId('');
|
||||
}, [resetTransaction]);
|
||||
|
||||
return {
|
||||
transaction,
|
||||
finalizedOrder,
|
||||
id,
|
||||
submit,
|
||||
reset,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This function creates an ID in the same way that core does on the backend. This way we
|
||||
* Can match up the newly created order with incoming orders via a subscription
|
||||
*/
|
||||
export const determineId = (sig: string) => {
|
||||
// Prepend 0x
|
||||
if (sig.slice(0, 2) !== '0x') {
|
||||
sig = '0x' + sig;
|
||||
}
|
||||
|
||||
// Create the ID
|
||||
const hash = new SHA3(256);
|
||||
const bytes = ethers.utils.arrayify(sig);
|
||||
hash.update(Buffer.from(bytes));
|
||||
const id = ethers.utils.hexlify(hash.digest());
|
||||
|
||||
// Remove 0x as core doesn't keep them in the API
|
||||
return id.substring(2);
|
||||
};
|
98
apps/trading/hooks/use-vega-transaction.spec.tsx
Normal file
98
apps/trading/hooks/use-vega-transaction.spec.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import {
|
||||
OrderSubmission,
|
||||
VegaWalletContext,
|
||||
VegaWalletContextShape,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { ReactNode } from 'react';
|
||||
import { useVegaTransaction, VegaTxStatus } from './use-vega-transaction';
|
||||
|
||||
const defaultWalletContext = {
|
||||
keypair: null,
|
||||
keypairs: [],
|
||||
sendTx: jest.fn(),
|
||||
connect: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
selectPublicKey: jest.fn(),
|
||||
connector: null,
|
||||
};
|
||||
|
||||
function setup(context?: Partial<VegaWalletContextShape>) {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<VegaWalletContext.Provider value={{ ...defaultWalletContext, ...context }}>
|
||||
{children}
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
return renderHook(() => useVegaTransaction(), { wrapper });
|
||||
}
|
||||
|
||||
test('Has the correct default state', () => {
|
||||
const { result } = setup();
|
||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
|
||||
expect(result.current.transaction.hash).toEqual(null);
|
||||
expect(result.current.transaction.signature).toEqual(null);
|
||||
expect(result.current.transaction.error).toEqual(null);
|
||||
expect(typeof result.current.reset).toEqual('function');
|
||||
expect(typeof result.current.send).toEqual('function');
|
||||
});
|
||||
|
||||
test('If provider returns null status should be default', async () => {
|
||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(null));
|
||||
const { result } = setup({ sendTx: mockSendTx });
|
||||
await act(async () => {
|
||||
result.current.send({} as OrderSubmission);
|
||||
});
|
||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
|
||||
});
|
||||
|
||||
test('Handles a single error', async () => {
|
||||
const errorMessage = 'Oops error!';
|
||||
const mockSendTx = jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve({ error: errorMessage }));
|
||||
const { result } = setup({ sendTx: mockSendTx });
|
||||
await act(async () => {
|
||||
result.current.send({} as OrderSubmission);
|
||||
});
|
||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Rejected);
|
||||
expect(result.current.transaction.error).toEqual({ error: errorMessage });
|
||||
});
|
||||
|
||||
test('Handles multiple errors', async () => {
|
||||
const errorObj = {
|
||||
errors: {
|
||||
something: 'Went wrong!',
|
||||
},
|
||||
};
|
||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(errorObj));
|
||||
const { result } = setup({ sendTx: mockSendTx });
|
||||
await act(async () => {
|
||||
result.current.send({} as OrderSubmission);
|
||||
});
|
||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Rejected);
|
||||
expect(result.current.transaction.error).toEqual(errorObj);
|
||||
});
|
||||
|
||||
test('Returns the signature if successful', async () => {
|
||||
const successObj = {
|
||||
tx: {
|
||||
inputData: 'input-data',
|
||||
signature: {
|
||||
algo: 'algo',
|
||||
version: 1,
|
||||
value: 'signature',
|
||||
},
|
||||
},
|
||||
txHash: '0x123',
|
||||
};
|
||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(successObj));
|
||||
const { result } = setup({ sendTx: mockSendTx });
|
||||
await act(async () => {
|
||||
result.current.send({} as OrderSubmission);
|
||||
});
|
||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Pending);
|
||||
expect(result.current.transaction.hash).toEqual(successObj.txHash);
|
||||
expect(result.current.transaction.signature).toEqual(
|
||||
successObj.tx.signature.value
|
||||
);
|
||||
});
|
89
apps/trading/hooks/use-vega-transaction.ts
Normal file
89
apps/trading/hooks/use-vega-transaction.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useVegaWallet, SendTxError, Transaction } from '@vegaprotocol/wallet';
|
||||
|
||||
export enum VegaTxStatus {
|
||||
Default = 'Default',
|
||||
AwaitingConfirmation = 'AwaitingConfirmation',
|
||||
Rejected = 'Rejected',
|
||||
Pending = 'Pending',
|
||||
}
|
||||
|
||||
export interface TransactionState {
|
||||
status: VegaTxStatus;
|
||||
error: object | null;
|
||||
hash: string | null;
|
||||
signature: string | null;
|
||||
}
|
||||
|
||||
export const useVegaTransaction = () => {
|
||||
const { sendTx } = useVegaWallet();
|
||||
const [transaction, _setTransaction] = useState<TransactionState>({
|
||||
status: VegaTxStatus.Default,
|
||||
error: null,
|
||||
hash: null,
|
||||
signature: null,
|
||||
});
|
||||
|
||||
const setTransaction = useCallback((update: Partial<TransactionState>) => {
|
||||
_setTransaction((curr) => ({
|
||||
...curr,
|
||||
...update,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleError = useCallback(
|
||||
(error: SendTxError) => {
|
||||
setTransaction({ error, status: VegaTxStatus.Rejected });
|
||||
},
|
||||
[setTransaction]
|
||||
);
|
||||
|
||||
const send = useCallback(
|
||||
async (tx: Transaction) => {
|
||||
setTransaction({
|
||||
error: null,
|
||||
hash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.AwaitingConfirmation,
|
||||
});
|
||||
|
||||
const res = await sendTx(tx);
|
||||
|
||||
if (res === null) {
|
||||
setTransaction({ status: VegaTxStatus.Default });
|
||||
return null;
|
||||
}
|
||||
|
||||
if ('error' in res) {
|
||||
handleError(res);
|
||||
return null;
|
||||
} else if ('errors' in res) {
|
||||
handleError(res);
|
||||
return null;
|
||||
} else if (res.tx && res.txHash) {
|
||||
setTransaction({
|
||||
status: VegaTxStatus.Pending,
|
||||
hash: res.txHash,
|
||||
signature: res.tx.signature.value,
|
||||
});
|
||||
return {
|
||||
signature: res.tx.signature?.value,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[sendTx, handleError, setTransaction]
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setTransaction({
|
||||
error: null,
|
||||
hash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Default,
|
||||
});
|
||||
}, [setTransaction]);
|
||||
|
||||
return { send, transaction, reset };
|
||||
};
|
@ -2,6 +2,7 @@ import {
|
||||
AgGridDynamic as AgGrid,
|
||||
Button,
|
||||
Callout,
|
||||
Intent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
|
||||
@ -15,7 +16,7 @@ export function Index() {
|
||||
<div className="m-24">
|
||||
<div className="mb-24">
|
||||
<Callout
|
||||
intent="help"
|
||||
intent={Intent.Help}
|
||||
title="Welcome to Vega Trading App"
|
||||
iconName="endorsed"
|
||||
headingLevel={1}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { Market, MarketVariables } from '@vegaprotocol/graphql';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { Market, MarketVariables } from './__generated__/Market';
|
||||
import { PageQueryContainer } from '../../components/page-query-container';
|
||||
import { TradeGrid, TradePanels } from './trade-grid';
|
||||
|
||||
@ -12,12 +13,34 @@ const MARKET_QUERY = gql`
|
||||
market(id: $marketId) {
|
||||
id
|
||||
name
|
||||
decimalPlaces
|
||||
state
|
||||
tradingMode
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
product {
|
||||
... on Future {
|
||||
quoteName
|
||||
settlementAsset {
|
||||
id
|
||||
symbol
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
trades {
|
||||
id
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
}
|
||||
depth {
|
||||
lastTrade {
|
||||
price
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -38,15 +61,20 @@ const MarketPage = () => {
|
||||
marketId: Array.isArray(marketId) ? marketId[0] : marketId,
|
||||
},
|
||||
skip: !marketId,
|
||||
fetchPolicy: 'network-only',
|
||||
}}
|
||||
>
|
||||
{({ market }) =>
|
||||
w > 1050 ? (
|
||||
{({ market }) => {
|
||||
if (!market) {
|
||||
return <Splash>Market not found</Splash>;
|
||||
}
|
||||
|
||||
return w > 960 ? (
|
||||
<TradeGrid market={market} />
|
||||
) : (
|
||||
<TradePanels market={market} />
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
</PageQueryContainer>
|
||||
);
|
||||
};
|
||||
|
55
apps/trading/pages/markets/__generated__/Market.ts
generated
55
apps/trading/pages/markets/__generated__/Market.ts
generated
@ -1,55 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: Market
|
||||
// ====================================================
|
||||
|
||||
export interface Market_market_trades {
|
||||
__typename: "Trade";
|
||||
/**
|
||||
* The hash of the trade data
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
|
||||
*/
|
||||
price: string;
|
||||
/**
|
||||
* The number of contracts trades, will always be <= the remaining size of both orders immediately before the trade (uint64)
|
||||
*/
|
||||
size: string;
|
||||
/**
|
||||
* RFC3339Nano time for when the trade occurred
|
||||
*/
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Market_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Market full name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Trades on a market
|
||||
*/
|
||||
trades: Market_market_trades[] | null;
|
||||
}
|
||||
|
||||
export interface Market {
|
||||
/**
|
||||
* An instrument that is trading on the VEGA network
|
||||
*/
|
||||
market: Market_market | null;
|
||||
}
|
||||
|
||||
export interface MarketVariables {
|
||||
marketId: string;
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { Markets } from '@vegaprotocol/graphql';
|
||||
import { PageQueryContainer } from '../../components/page-query-container';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Markets } from './__generated__/Markets';
|
||||
|
||||
const MARKETS_QUERY = gql`
|
||||
query Markets {
|
||||
|
@ -1,9 +1,26 @@
|
||||
import { Market_market } from '@vegaprotocol/graphql';
|
||||
import classNames from 'classnames';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { useState, ReactNode } from 'react';
|
||||
import { TradingView, TradingViews } from '@vegaprotocol/react-helpers';
|
||||
import { Market_market } from './__generated__/Market';
|
||||
import { GridTab, GridTabs } from './grid-tabs';
|
||||
import { DealTicketContainer } from '../../components/deal-ticket-container';
|
||||
|
||||
const Chart = () => <div>TODO: Chart</div>;
|
||||
const Orderbook = () => <div>TODO: Orderbook</div>;
|
||||
const Orders = () => <div>TODO: Orders</div>;
|
||||
const Positions = () => <div>TODO: Positions</div>;
|
||||
const Collateral = () => <div>TODO: Collateral</div>;
|
||||
|
||||
type TradingView = keyof typeof TradingViews;
|
||||
|
||||
const TradingViews = {
|
||||
chart: Chart,
|
||||
ticket: DealTicketContainer,
|
||||
orderbook: Orderbook,
|
||||
orders: Orders,
|
||||
positions: Positions,
|
||||
collateral: Collateral,
|
||||
};
|
||||
|
||||
interface TradeGridProps {
|
||||
market: Market_market;
|
||||
@ -25,7 +42,7 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
|
||||
<TradingViews.chart />
|
||||
</TradeGridChild>
|
||||
<TradeGridChild className="row-start-1 row-end-3">
|
||||
<TradingViews.ticket />
|
||||
<TradingViews.ticket market={market} />
|
||||
</TradeGridChild>
|
||||
<TradeGridChild className="row-start-1 row-end-3">
|
||||
<GridTabs group="trade">
|
||||
@ -88,7 +105,7 @@ export const TradePanels = ({ market }: TradePanelsProps) => {
|
||||
throw new Error(`No component for view: ${view}`);
|
||||
}
|
||||
|
||||
return <Component />;
|
||||
return <Component market={market} />;
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -54,7 +54,7 @@
|
||||
"options": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "npx apollo client:codegen --config=apps/trading/apollo.config.js --target=typescript --globalTypesFile=apps/trading/__generated__/globalTypes.ts"
|
||||
"command": "npx apollo client:codegen libs/graphql/src/lib/ --config=apps/trading/apollo.config.js --target=typescript --globalTypesFile=libs/graphql/src/lib/globalTypes.ts --outputFlat"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
12
libs/deal-ticket/.babelrc
Normal file
12
libs/deal-ticket/.babelrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nrwl/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
18
libs/deal-ticket/.eslintrc.json
Normal file
18
libs/deal-ticket/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
7
libs/deal-ticket/README.md
Normal file
7
libs/deal-ticket/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# deal-ticket
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test deal-ticket` to execute the unit tests via [Jest](https://jestjs.io).
|
9
libs/deal-ticket/jest.config.js
Normal file
9
libs/deal-ticket/jest.config.js
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
displayName: 'deal-ticket',
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/libs/deal-ticket',
|
||||
};
|
4
libs/deal-ticket/package.json
Normal file
4
libs/deal-ticket/package.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/deal-ticket",
|
||||
"version": "0.0.1"
|
||||
}
|
43
libs/deal-ticket/project.json
Normal file
43
libs/deal-ticket/project.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"root": "libs/deal-ticket",
|
||||
"sourceRoot": "libs/deal-ticket/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nrwl/web:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/deal-ticket",
|
||||
"tsConfig": "libs/deal-ticket/tsconfig.lib.json",
|
||||
"project": "libs/deal-ticket/package.json",
|
||||
"entryFile": "libs/deal-ticket/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/deal-ticket/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/deal-ticket/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["coverage/libs/deal-ticket"],
|
||||
"options": {
|
||||
"jestConfig": "libs/deal-ticket/jest.config.js",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
libs/deal-ticket/src/button-radio.tsx
Normal file
38
libs/deal-ticket/src/button-radio.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface ButtonRadioProps {
|
||||
name: string;
|
||||
options: Array<{ value: string; text: string }>;
|
||||
currentOption: string | null;
|
||||
onSelect: (option: string) => void;
|
||||
}
|
||||
|
||||
export const ButtonRadio = ({
|
||||
name,
|
||||
options,
|
||||
currentOption,
|
||||
onSelect,
|
||||
}: ButtonRadioProps) => {
|
||||
return (
|
||||
<div className="flex gap-8">
|
||||
{options.map((option) => {
|
||||
const isSelected = option.value === currentOption;
|
||||
return (
|
||||
<Button
|
||||
onClick={() => onSelect(option.value)}
|
||||
className="flex-1"
|
||||
variant={isSelected ? 'accent' : undefined}
|
||||
data-testid={
|
||||
isSelected
|
||||
? `${name}-${option.value}-selected`
|
||||
: `${name}-${option.value}`
|
||||
}
|
||||
key={option.value}
|
||||
>
|
||||
{option.text}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
78
libs/deal-ticket/src/deal-ticket-limit.tsx
Normal file
78
libs/deal-ticket/src/deal-ticket-limit.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderTimeInForce } from '@vegaprotocol/wallet';
|
||||
import { TransactionStatus } from './deal-ticket';
|
||||
import { Market_market } from '@vegaprotocol/graphql';
|
||||
import { ExpirySelector } from './expiry-selector';
|
||||
import { SideSelector } from './side-selector';
|
||||
import { SubmitButton } from './submit-button';
|
||||
import { TimeInForceSelector } from './time-in-force-selector';
|
||||
import { TypeSelector } from './type-selector';
|
||||
import { Order } from './use-order-state';
|
||||
|
||||
interface DealTicketLimitProps {
|
||||
order: Order;
|
||||
updateOrder: (order: Partial<Order>) => void;
|
||||
transactionStatus: TransactionStatus;
|
||||
market: Market_market;
|
||||
}
|
||||
|
||||
export const DealTicketLimit = ({
|
||||
order,
|
||||
updateOrder,
|
||||
transactionStatus,
|
||||
market,
|
||||
}: DealTicketLimitProps) => {
|
||||
return (
|
||||
<>
|
||||
<TypeSelector order={order} onSelect={(type) => updateOrder({ type })} />
|
||||
<SideSelector order={order} onSelect={(side) => updateOrder({ side })} />
|
||||
<div className="flex items-center gap-8">
|
||||
<div className="flex-1">
|
||||
<FormGroup label="Amount">
|
||||
<Input
|
||||
value={order.size}
|
||||
onChange={(e) => updateOrder({ size: e.target.value })}
|
||||
className="w-full"
|
||||
type="number"
|
||||
data-testid="order-size"
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div>@</div>
|
||||
<div className="flex-1">
|
||||
<FormGroup
|
||||
label={`Price (${market.tradableInstrument.instrument.product.quoteName})`}
|
||||
labelAlign="right"
|
||||
>
|
||||
<Input
|
||||
value={order.price}
|
||||
onChange={(e) => updateOrder({ price: e.target.value })}
|
||||
className="w-full"
|
||||
type="number"
|
||||
data-testid="order-price"
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
</div>
|
||||
<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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
63
libs/deal-ticket/src/deal-ticket-market.tsx
Normal file
63
libs/deal-ticket/src/deal-ticket-market.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
|
||||
import { Market_market } from '@vegaprotocol/graphql';
|
||||
import { TransactionStatus } from './deal-ticket';
|
||||
import { SideSelector } from './side-selector';
|
||||
import { SubmitButton } from './submit-button';
|
||||
import { TimeInForceSelector } from './time-in-force-selector';
|
||||
import { TypeSelector } from './type-selector';
|
||||
import { Order } from './use-order-state';
|
||||
|
||||
interface DealTicketMarketProps {
|
||||
order: Order;
|
||||
updateOrder: (order: Partial<Order>) => void;
|
||||
transactionStatus: TransactionStatus;
|
||||
market: Market_market;
|
||||
}
|
||||
|
||||
export const DealTicketMarket = ({
|
||||
order,
|
||||
updateOrder,
|
||||
transactionStatus,
|
||||
market,
|
||||
}: DealTicketMarketProps) => {
|
||||
return (
|
||||
<>
|
||||
<TypeSelector order={order} onSelect={(type) => updateOrder({ type })} />
|
||||
<SideSelector order={order} onSelect={(side) => updateOrder({ side })} />
|
||||
<div className="flex items-center gap-8">
|
||||
<div className="flex-1">
|
||||
<FormGroup label="Amount">
|
||||
<Input
|
||||
value={order.size}
|
||||
onChange={(e) => updateOrder({ size: e.target.value })}
|
||||
className="w-full"
|
||||
type="number"
|
||||
data-testid="order-size"
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div className="pt-4">@</div>
|
||||
<div className="flex-1 pt-4" data-testid="last-price">
|
||||
{market.depth.lastTrade ? (
|
||||
<>
|
||||
~{addDecimal(market.depth.lastTrade.price, market.decimalPlaces)}{' '}
|
||||
{market.tradableInstrument.instrument.product.quoteName}
|
||||
</>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<TimeInForceSelector
|
||||
order={order}
|
||||
onSelect={(timeInForce) => updateOrder({ timeInForce })}
|
||||
/>
|
||||
<SubmitButton
|
||||
transactionStatus={transactionStatus}
|
||||
market={market}
|
||||
order={order}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
141
libs/deal-ticket/src/deal-ticket.spec.tsx
Normal file
141
libs/deal-ticket/src/deal-ticket.spec.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import {
|
||||
VegaWalletContext,
|
||||
OrderTimeInForce,
|
||||
OrderType,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { DealTicket, Market } from './deal-ticket';
|
||||
import { Order } from './use-order-state';
|
||||
|
||||
const order: Order = {
|
||||
type: OrderType.Market,
|
||||
size: '100',
|
||||
timeInForce: OrderTimeInForce.FOK,
|
||||
side: null,
|
||||
};
|
||||
const market: Market = {
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
tradingMode: 'Continuous',
|
||||
state: 'Active',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
quoteName: 'quote-name',
|
||||
settlementAsset: {
|
||||
id: 'asset-id',
|
||||
symbol: 'asset-symbol',
|
||||
name: 'asset-name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
depth: {
|
||||
lastTrade: {
|
||||
price: '100',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function generateJsx() {
|
||||
return (
|
||||
<VegaWalletContext.Provider value={{} as any}>
|
||||
<DealTicket defaultOrder={order} market={market} />
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
test('Deal ticket defaults', () => {
|
||||
render(generateJsx());
|
||||
|
||||
// Assert defaults are used
|
||||
expect(
|
||||
screen.getByTestId(`order-type-${order.type}-selected`)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_BUY-selected')
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_SELL-selected')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue(order.size);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(order.timeInForce);
|
||||
|
||||
// Assert last price is shown
|
||||
expect(screen.getByTestId('last-price')).toHaveTextContent(
|
||||
// eslint-disable-next-line
|
||||
`~${addDecimal(market.depth.lastTrade!.price, market.decimalPlaces)} ${
|
||||
market.tradableInstrument.instrument.product.quoteName
|
||||
}`
|
||||
);
|
||||
});
|
||||
|
||||
test('Can edit deal ticket', () => {
|
||||
render(generateJsx());
|
||||
|
||||
// Asssert changing values
|
||||
fireEvent.click(screen.getByTestId('order-side-SIDE_BUY'));
|
||||
expect(
|
||||
screen.getByTestId('order-side-SIDE_BUY-selected')
|
||||
).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByTestId('order-size'), {
|
||||
target: { value: '200' },
|
||||
});
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue('200');
|
||||
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: OrderTimeInForce.IOC },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.IOC);
|
||||
|
||||
// Switch to limit order
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
|
||||
|
||||
// Assert price input shown with default value
|
||||
expect(screen.getByTestId('order-price')).toHaveDisplayValue('0');
|
||||
|
||||
// Check all TIF options shown
|
||||
expect(screen.getByTestId('order-tif').children).toHaveLength(
|
||||
Object.keys(OrderTimeInForce).length
|
||||
);
|
||||
});
|
||||
|
||||
test('Handles TIF select box dependent on order type', () => {
|
||||
render(generateJsx());
|
||||
|
||||
// Check only IOC and
|
||||
expect(
|
||||
Array.from(screen.getByTestId('order-tif').children).map(
|
||||
(o) => o.textContent
|
||||
)
|
||||
).toEqual(['IOC', 'FOK']);
|
||||
|
||||
// Switch to limit order and check all TIF options shown
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
|
||||
expect(screen.getByTestId('order-tif').children).toHaveLength(
|
||||
Object.keys(OrderTimeInForce).length
|
||||
);
|
||||
|
||||
// Change to GTC
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: OrderTimeInForce.GTC },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.GTC);
|
||||
|
||||
// Switch back to market order and TIF should now be IOC
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_MARKET'));
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.IOC);
|
||||
|
||||
// Switch tif to FOK
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: OrderTimeInForce.FOK },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.FOK);
|
||||
|
||||
// Change back to limit and check we are still on FOK
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.FOK);
|
||||
});
|
69
libs/deal-ticket/src/deal-ticket.tsx
Normal file
69
libs/deal-ticket/src/deal-ticket.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import { Market_market } from '@vegaprotocol/graphql';
|
||||
import { FormEvent } from 'react';
|
||||
import { Order, useOrderState } from './use-order-state';
|
||||
import { DealTicketMarket } from './deal-ticket-market';
|
||||
import { DealTicketLimit } from './deal-ticket-limit';
|
||||
|
||||
const DEFAULT_ORDER: Order = {
|
||||
type: OrderType.Market,
|
||||
side: OrderSide.Buy,
|
||||
size: '1',
|
||||
timeInForce: OrderTimeInForce.IOC,
|
||||
};
|
||||
|
||||
// TODO: Consider using a generated type when we have a better solution for
|
||||
// sharing the types from GQL
|
||||
|
||||
export type TransactionStatus = 'default' | 'pending';
|
||||
|
||||
export interface DealTicketProps {
|
||||
market: Market_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>
|
||||
);
|
||||
};
|
26
libs/deal-ticket/src/expiry-selector.tsx
Normal file
26
libs/deal-ticket/src/expiry-selector.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
|
||||
import { Order } from './use-order-state';
|
||||
import { formatForInput } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface ExpirySelectorProps {
|
||||
order: Order;
|
||||
onSelect: (expiration: Date | null) => void;
|
||||
}
|
||||
|
||||
export const ExpirySelector = ({ order, onSelect }: ExpirySelectorProps) => {
|
||||
const date = order.expiration ? new Date(order.expiration) : new Date();
|
||||
const dateFormatted = formatForInput(date);
|
||||
const minDate = formatForInput(date);
|
||||
return (
|
||||
<FormGroup label="Expiry time/date" labelFor="expiration">
|
||||
<Input
|
||||
id="expiration"
|
||||
name="expiration"
|
||||
type="datetime-local"
|
||||
value={dateFormatted}
|
||||
onChange={(e) => onSelect(new Date(e.target.value))}
|
||||
min={minDate}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
2
libs/deal-ticket/src/index.ts
Normal file
2
libs/deal-ticket/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './deal-ticket';
|
||||
export * from './use-order-state';
|
25
libs/deal-ticket/src/side-selector.tsx
Normal file
25
libs/deal-ticket/src/side-selector.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { FormGroup } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderSide } from '@vegaprotocol/wallet';
|
||||
import { ButtonRadio } from './button-radio';
|
||||
import { Order } from './use-order-state';
|
||||
|
||||
interface SideSelectorProps {
|
||||
order: Order;
|
||||
onSelect: (side: OrderSide) => void;
|
||||
}
|
||||
|
||||
export const SideSelector = ({ order, onSelect }: SideSelectorProps) => {
|
||||
return (
|
||||
<FormGroup label="Direction">
|
||||
<ButtonRadio
|
||||
name="order-side"
|
||||
options={Object.entries(OrderSide).map(([text, value]) => ({
|
||||
text,
|
||||
value,
|
||||
}))}
|
||||
currentOption={order.side}
|
||||
onSelect={(value) => onSelect(value as OrderSide)}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
78
libs/deal-ticket/src/submit-button.tsx
Normal file
78
libs/deal-ticket/src/submit-button.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import { Market_market } from '@vegaprotocol/graphql';
|
||||
import { useMemo } from 'react';
|
||||
import { Order } from './use-order-state';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { TransactionStatus } from './deal-ticket';
|
||||
|
||||
interface SubmitButtonProps {
|
||||
transactionStatus: TransactionStatus;
|
||||
market: Market_market;
|
||||
order: Order;
|
||||
}
|
||||
|
||||
export const SubmitButton = ({
|
||||
market,
|
||||
transactionStatus,
|
||||
order,
|
||||
}: SubmitButtonProps) => {
|
||||
const { keypair } = useVegaWallet();
|
||||
|
||||
const invalidText = useMemo(() => {
|
||||
if (!keypair) {
|
||||
return 'No public key selected';
|
||||
}
|
||||
|
||||
if (keypair.tainted) {
|
||||
return 'Selected public key has been tainted';
|
||||
}
|
||||
|
||||
// TODO: Change these to use enums from @vegaprotocol/graphql
|
||||
if (market.state !== 'Active') {
|
||||
if (market.state === 'Suspended') {
|
||||
return 'Market is currently suspended';
|
||||
}
|
||||
|
||||
if (market.state === 'Proposed' || market.state === 'Pending') {
|
||||
return 'Market is not active yet';
|
||||
}
|
||||
|
||||
return 'Market is no longer active';
|
||||
}
|
||||
|
||||
if (market.tradingMode !== 'Continuous') {
|
||||
if (order.type === OrderType.Market) {
|
||||
return 'Only limit orders are permitted when market is in auction';
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
OrderTimeInForce.FOK,
|
||||
OrderTimeInForce.IOC,
|
||||
OrderTimeInForce.GFN,
|
||||
].includes(order.timeInForce)
|
||||
) {
|
||||
return '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}
|
||||
>
|
||||
{transactionStatus === 'pending' ? 'Pending...' : 'Place order'}
|
||||
</Button>
|
||||
{invalidText && <InputError className="mb-8">{invalidText}</InputError>}
|
||||
</>
|
||||
);
|
||||
};
|
40
libs/deal-ticket/src/time-in-force-selector.tsx
Normal file
40
libs/deal-ticket/src/time-in-force-selector.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { FormGroup, Select } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import { Order } from './use-order-state';
|
||||
|
||||
interface TimeInForceSelectorProps {
|
||||
order: Order;
|
||||
onSelect: (tif: OrderTimeInForce) => void;
|
||||
}
|
||||
|
||||
export const TimeInForceSelector = ({
|
||||
order,
|
||||
onSelect,
|
||||
}: TimeInForceSelectorProps) => {
|
||||
const options =
|
||||
order.type === OrderType.Limit
|
||||
? Object.entries(OrderTimeInForce)
|
||||
: Object.entries(OrderTimeInForce).filter(
|
||||
([key, value]) =>
|
||||
value === OrderTimeInForce.FOK || value === OrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup label="Time in force">
|
||||
<Select
|
||||
value={order.timeInForce}
|
||||
onChange={(e) => onSelect(e.target.value as OrderTimeInForce)}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
>
|
||||
{options.map(([key, value]) => {
|
||||
return (
|
||||
<option key={key} value={value}>
|
||||
{key}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
25
libs/deal-ticket/src/type-selector.tsx
Normal file
25
libs/deal-ticket/src/type-selector.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { FormGroup } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderType } from '@vegaprotocol/wallet';
|
||||
import { ButtonRadio } from './button-radio';
|
||||
import { Order } from './use-order-state';
|
||||
|
||||
interface TypeSelectorProps {
|
||||
order: Order;
|
||||
onSelect: (type: OrderType) => void;
|
||||
}
|
||||
|
||||
export const TypeSelector = ({ order, onSelect }: TypeSelectorProps) => {
|
||||
return (
|
||||
<FormGroup label="Order type">
|
||||
<ButtonRadio
|
||||
name="order-type"
|
||||
options={Object.entries(OrderType).map(([text, value]) => ({
|
||||
text,
|
||||
value,
|
||||
}))}
|
||||
currentOption={order.type}
|
||||
onSelect={(value) => onSelect(value as OrderType)}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
78
libs/deal-ticket/src/use-order-state.ts
Normal file
78
libs/deal-ticket/src/use-order-state.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { OrderSide, 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];
|
||||
};
|
25
libs/deal-ticket/tsconfig.json
Normal file
25
libs/deal-ticket/tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
22
libs/deal-ticket/tsconfig.lib.json
Normal file
22
libs/deal-ticket/tsconfig.lib.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx"
|
||||
],
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
19
libs/deal-ticket/tsconfig.spec.json
Normal file
19
libs/deal-ticket/tsconfig.spec.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.jsx",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
7
libs/graphql/README.md
Normal file
7
libs/graphql/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# graphql
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Building
|
||||
|
||||
Run `nx build graphql` to build the library.
|
5
libs/graphql/package.json
Normal file
5
libs/graphql/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@vegaprotocol/graphql",
|
||||
"version": "0.0.1",
|
||||
"type": "commonjs"
|
||||
}
|
18
libs/graphql/project.json
Normal file
18
libs/graphql/project.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"root": "libs/graphql",
|
||||
"sourceRoot": "libs/graphql/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nrwl/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/graphql",
|
||||
"main": "libs/graphql/src/index.ts",
|
||||
"tsConfig": "libs/graphql/tsconfig.lib.json",
|
||||
"assets": ["libs/graphql/*.md"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
4
libs/graphql/src/index.ts
Normal file
4
libs/graphql/src/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './lib/globalTypes';
|
||||
export * from './lib/Market';
|
||||
export * from './lib/Markets';
|
||||
export * from './lib/OrderEvent';
|
150
libs/graphql/src/lib/Market.ts
Normal file
150
libs/graphql/src/lib/Market.ts
Normal file
@ -0,0 +1,150 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { MarketState, MarketTradingMode } from './globalTypes';
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: Market
|
||||
// ====================================================
|
||||
|
||||
export interface Market_market_tradableInstrument_instrument_product_settlementAsset {
|
||||
__typename: 'Asset';
|
||||
/**
|
||||
* The id of the asset
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The symbol of the asset (e.g: GBP)
|
||||
*/
|
||||
symbol: string;
|
||||
/**
|
||||
* The full name of the asset (e.g: Great British Pound)
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Market_market_tradableInstrument_instrument_product {
|
||||
__typename: 'Future';
|
||||
/**
|
||||
* String representing the quote (e.g. BTCUSD -> USD is quote)
|
||||
*/
|
||||
quoteName: string;
|
||||
/**
|
||||
* The name of the asset (string)
|
||||
*/
|
||||
settlementAsset: Market_market_tradableInstrument_instrument_product_settlementAsset;
|
||||
}
|
||||
|
||||
export interface Market_market_tradableInstrument_instrument {
|
||||
__typename: 'Instrument';
|
||||
/**
|
||||
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
|
||||
*/
|
||||
product: Market_market_tradableInstrument_instrument_product;
|
||||
}
|
||||
|
||||
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_trades {
|
||||
__typename: 'Trade';
|
||||
/**
|
||||
* The hash of the trade data
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
|
||||
*/
|
||||
price: string;
|
||||
/**
|
||||
* The number of contracts trades, will always be <= the remaining size of both orders immediately before the trade (uint64)
|
||||
*/
|
||||
size: string;
|
||||
/**
|
||||
* RFC3339Nano time for when the trade occurred
|
||||
*/
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Market_market_depth_lastTrade {
|
||||
__typename: 'Trade';
|
||||
/**
|
||||
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
|
||||
*/
|
||||
price: string;
|
||||
}
|
||||
|
||||
export interface Market_market_depth {
|
||||
__typename: 'MarketDepth';
|
||||
/**
|
||||
* Last trade for the given market (if available)
|
||||
*/
|
||||
lastTrade: Market_market_depth_lastTrade | null;
|
||||
}
|
||||
|
||||
export interface Market_market {
|
||||
__typename: '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)
|
||||
*
|
||||
* 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;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
state: MarketState;
|
||||
/**
|
||||
* Current mode of execution of the market
|
||||
*/
|
||||
tradingMode: MarketTradingMode;
|
||||
/**
|
||||
* An instance of or reference to a tradable instrument.
|
||||
*/
|
||||
tradableInstrument: Market_market_tradableInstrument;
|
||||
/**
|
||||
* Trades on a market
|
||||
*/
|
||||
trades: Market_market_trades[] | null;
|
||||
/**
|
||||
* Current depth on the order book for this market
|
||||
*/
|
||||
depth: Market_market_depth;
|
||||
}
|
||||
|
||||
export interface Market {
|
||||
/**
|
||||
* An instrument that is trading on the VEGA network
|
||||
*/
|
||||
market: Market_market | null;
|
||||
}
|
||||
|
||||
export interface MarketVariables {
|
||||
marketId: string;
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
// ====================================================
|
||||
|
||||
export interface Markets_markets {
|
||||
__typename: "Market";
|
||||
__typename: 'Market';
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
122
libs/graphql/src/lib/OrderEvent.ts
Normal file
122
libs/graphql/src/lib/OrderEvent.ts
Normal file
@ -0,0 +1,122 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import {
|
||||
BusEventType,
|
||||
OrderType,
|
||||
OrderStatus,
|
||||
OrderRejectionReason,
|
||||
} from './globalTypes';
|
||||
|
||||
// ====================================================
|
||||
// GraphQL subscription operation: OrderEvent
|
||||
// ====================================================
|
||||
|
||||
export interface OrderEvent_busEvents_event_TimeUpdate {
|
||||
__typename:
|
||||
| 'TimeUpdate'
|
||||
| 'MarketEvent'
|
||||
| 'TransferResponses'
|
||||
| 'PositionResolution'
|
||||
| 'Trade'
|
||||
| 'Account'
|
||||
| 'Party'
|
||||
| 'MarginLevels'
|
||||
| 'Proposal'
|
||||
| 'Vote'
|
||||
| 'MarketData'
|
||||
| 'NodeSignature'
|
||||
| 'LossSocialization'
|
||||
| 'SettlePosition'
|
||||
| 'Market'
|
||||
| 'Asset'
|
||||
| 'MarketTick'
|
||||
| 'SettleDistressed'
|
||||
| 'AuctionEvent'
|
||||
| 'RiskFactor'
|
||||
| 'Deposit'
|
||||
| 'Withdrawal'
|
||||
| 'OracleSpec'
|
||||
| 'LiquidityProvision';
|
||||
}
|
||||
|
||||
export interface OrderEvent_busEvents_event_Order_market {
|
||||
__typename: 'Market';
|
||||
/**
|
||||
* Market full name
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface OrderEvent_busEvents_event_Order {
|
||||
__typename: 'Order';
|
||||
/**
|
||||
* Type the order type (defaults to PARTY)
|
||||
*/
|
||||
type: OrderType | null;
|
||||
/**
|
||||
* Hash of the order data
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The status of an order, for example 'Active'
|
||||
*/
|
||||
status: OrderStatus;
|
||||
/**
|
||||
* Reason for the order to be rejected
|
||||
*/
|
||||
rejectionReason: OrderRejectionReason | null;
|
||||
/**
|
||||
* RFC3339Nano formatted date and time for when the order was created (timestamp)
|
||||
*/
|
||||
createdAt: string;
|
||||
/**
|
||||
* Total number of contracts that may be bought or sold (immutable) (uint64)
|
||||
*/
|
||||
size: string;
|
||||
/**
|
||||
* The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64)
|
||||
*/
|
||||
price: string;
|
||||
/**
|
||||
* The market the order is trading on (probably stored internally as a hash of the market details)
|
||||
*/
|
||||
market: OrderEvent_busEvents_event_Order_market | null;
|
||||
}
|
||||
|
||||
export type OrderEvent_busEvents_event =
|
||||
| OrderEvent_busEvents_event_TimeUpdate
|
||||
| OrderEvent_busEvents_event_Order;
|
||||
|
||||
export interface OrderEvent_busEvents {
|
||||
__typename: 'BusEvent';
|
||||
/**
|
||||
* the id for this event
|
||||
*/
|
||||
eventId: string;
|
||||
/**
|
||||
* the block hash
|
||||
*/
|
||||
block: string;
|
||||
/**
|
||||
* the type of event we're dealing with
|
||||
*/
|
||||
type: BusEventType;
|
||||
/**
|
||||
* the payload - the wrapped event
|
||||
*/
|
||||
event: OrderEvent_busEvents_event;
|
||||
}
|
||||
|
||||
export interface OrderEvent {
|
||||
/**
|
||||
* Subscribe to event data from the event bus
|
||||
*/
|
||||
busEvents: OrderEvent_busEvents[] | null;
|
||||
}
|
||||
|
||||
export interface OrderEventVariables {
|
||||
partyId: string;
|
||||
}
|
137
libs/graphql/src/lib/globalTypes.ts
Normal file
137
libs/graphql/src/lib/globalTypes.ts
Normal file
@ -0,0 +1,137 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
//==============================================================
|
||||
// START Enums and Input Objects
|
||||
//==============================================================
|
||||
|
||||
export enum BusEventType {
|
||||
Account = 'Account',
|
||||
Asset = 'Asset',
|
||||
Auction = 'Auction',
|
||||
Deposit = 'Deposit',
|
||||
LiquidityProvision = 'LiquidityProvision',
|
||||
LossSocialization = 'LossSocialization',
|
||||
MarginLevels = 'MarginLevels',
|
||||
Market = 'Market',
|
||||
MarketCreated = 'MarketCreated',
|
||||
MarketData = 'MarketData',
|
||||
MarketTick = 'MarketTick',
|
||||
MarketUpdated = 'MarketUpdated',
|
||||
NodeSignature = 'NodeSignature',
|
||||
OracleSpec = 'OracleSpec',
|
||||
Order = 'Order',
|
||||
Party = 'Party',
|
||||
PositionResolution = 'PositionResolution',
|
||||
Proposal = 'Proposal',
|
||||
RiskFactor = 'RiskFactor',
|
||||
SettleDistressed = 'SettleDistressed',
|
||||
SettlePosition = 'SettlePosition',
|
||||
TimeUpdate = 'TimeUpdate',
|
||||
Trade = 'Trade',
|
||||
TransferResponses = 'TransferResponses',
|
||||
Vote = 'Vote',
|
||||
Withdrawal = 'Withdrawal',
|
||||
}
|
||||
|
||||
/**
|
||||
* The current state of a market
|
||||
*/
|
||||
export enum MarketState {
|
||||
Active = 'Active',
|
||||
Cancelled = 'Cancelled',
|
||||
Closed = 'Closed',
|
||||
Pending = 'Pending',
|
||||
Proposed = 'Proposed',
|
||||
Rejected = 'Rejected',
|
||||
Settled = 'Settled',
|
||||
Suspended = 'Suspended',
|
||||
TradingTerminated = 'TradingTerminated',
|
||||
}
|
||||
|
||||
/**
|
||||
* What market trading mode are we in
|
||||
*/
|
||||
export enum MarketTradingMode {
|
||||
BatchAuction = 'BatchAuction',
|
||||
Continuous = 'Continuous',
|
||||
MonitoringAuction = 'MonitoringAuction',
|
||||
OpeningAuction = 'OpeningAuction',
|
||||
}
|
||||
|
||||
/**
|
||||
* Reason for the order being rejected by the core node
|
||||
*/
|
||||
export enum OrderRejectionReason {
|
||||
AmendToGTTWithoutExpiryAt = 'AmendToGTTWithoutExpiryAt',
|
||||
CannotAmendFromGFAOrGFN = 'CannotAmendFromGFAOrGFN',
|
||||
CannotAmendPeggedOrderDetailsOnNonPeggedOrder = 'CannotAmendPeggedOrderDetailsOnNonPeggedOrder',
|
||||
CannotAmendToFOKOrIOC = 'CannotAmendToFOKOrIOC',
|
||||
CannotAmendToGFAOrGFN = 'CannotAmendToGFAOrGFN',
|
||||
EditNotAllowed = 'EditNotAllowed',
|
||||
ExpiryAtBeforeCreatedAt = 'ExpiryAtBeforeCreatedAt',
|
||||
FOKOrderDuringAuction = 'FOKOrderDuringAuction',
|
||||
GFAOrderDuringContinuousTrading = 'GFAOrderDuringContinuousTrading',
|
||||
GFNOrderDuringAuction = 'GFNOrderDuringAuction',
|
||||
GTCWithExpiryAtNotValid = 'GTCWithExpiryAtNotValid',
|
||||
IOCOrderDuringAuction = 'IOCOrderDuringAuction',
|
||||
InsufficientAssetBalance = 'InsufficientAssetBalance',
|
||||
InsufficientFundsToPayFees = 'InsufficientFundsToPayFees',
|
||||
InternalError = 'InternalError',
|
||||
InvalidExpirationTime = 'InvalidExpirationTime',
|
||||
InvalidMarketId = 'InvalidMarketId',
|
||||
InvalidMarketType = 'InvalidMarketType',
|
||||
InvalidOrderId = 'InvalidOrderId',
|
||||
InvalidOrderReference = 'InvalidOrderReference',
|
||||
InvalidPartyId = 'InvalidPartyId',
|
||||
InvalidPersistence = 'InvalidPersistence',
|
||||
InvalidRemainingSize = 'InvalidRemainingSize',
|
||||
InvalidSize = 'InvalidSize',
|
||||
InvalidTimeInForce = 'InvalidTimeInForce',
|
||||
InvalidType = 'InvalidType',
|
||||
MarginCheckFailed = 'MarginCheckFailed',
|
||||
MarketClosed = 'MarketClosed',
|
||||
MissingGeneralAccount = 'MissingGeneralAccount',
|
||||
NonPersistentOrderExceedsPriceBounds = 'NonPersistentOrderExceedsPriceBounds',
|
||||
OrderAmendFailure = 'OrderAmendFailure',
|
||||
OrderNotFound = 'OrderNotFound',
|
||||
OrderOutOfSequence = 'OrderOutOfSequence',
|
||||
OrderRemovalFailure = 'OrderRemovalFailure',
|
||||
PeggedOrderBuyCannotReferenceBestAskPrice = 'PeggedOrderBuyCannotReferenceBestAskPrice',
|
||||
PeggedOrderMustBeGTTOrGTC = 'PeggedOrderMustBeGTTOrGTC',
|
||||
PeggedOrderMustBeLimitOrder = 'PeggedOrderMustBeLimitOrder',
|
||||
PeggedOrderOffsetMustBeGreaterOrEqualToZero = 'PeggedOrderOffsetMustBeGreaterOrEqualToZero',
|
||||
PeggedOrderOffsetMustBeGreaterThanZero = 'PeggedOrderOffsetMustBeGreaterThanZero',
|
||||
PeggedOrderSellCannotReferenceBestBidPrice = 'PeggedOrderSellCannotReferenceBestBidPrice',
|
||||
PeggedOrderWithoutReferencePrice = 'PeggedOrderWithoutReferencePrice',
|
||||
SelfTrading = 'SelfTrading',
|
||||
TimeFailure = 'TimeFailure',
|
||||
UnableToAmendPeggedOrderPrice = 'UnableToAmendPeggedOrderPrice',
|
||||
UnableToRepricePeggedOrder = 'UnableToRepricePeggedOrder',
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid order statuses, these determine several states for an order that cannot be expressed with other fields in Order.
|
||||
*/
|
||||
export enum OrderStatus {
|
||||
Active = 'Active',
|
||||
Cancelled = 'Cancelled',
|
||||
Expired = 'Expired',
|
||||
Filled = 'Filled',
|
||||
Parked = 'Parked',
|
||||
PartiallyFilled = 'PartiallyFilled',
|
||||
Rejected = 'Rejected',
|
||||
Stopped = 'Stopped',
|
||||
}
|
||||
|
||||
export enum OrderType {
|
||||
Limit = 'Limit',
|
||||
Market = 'Market',
|
||||
Network = 'Network',
|
||||
}
|
||||
|
||||
//==============================================================
|
||||
// END Enums and Input Objects
|
||||
//==============================================================
|
19
libs/graphql/tsconfig.json
Normal file
19
libs/graphql/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
10
libs/graphql/tsconfig.lib.json
Normal file
10
libs/graphql/tsconfig.lib.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": []
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["**/*.spec.ts"]
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export * from './lib/context';
|
||||
export * from './lib/storage';
|
||||
export * from './lib/trading';
|
||||
export * from './lib/datetime';
|
||||
export * from './lib/decimals';
|
||||
|
13
libs/react-helpers/src/lib/datetime/datetime.ts
Normal file
13
libs/react-helpers/src/lib/datetime/datetime.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/** Returns date in a format suitable for input[type=date] elements */
|
||||
export const formatForInput = (date: Date) => {
|
||||
const padZero = (num: number) => num.toString().padStart(2, '0');
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = padZero(date.getMonth() + 1);
|
||||
const day = padZero(date.getDate());
|
||||
const hours = padZero(date.getHours());
|
||||
const minutes = padZero(date.getMinutes());
|
||||
const secs = padZero(date.getSeconds());
|
||||
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}:${secs}`;
|
||||
};
|
1
libs/react-helpers/src/lib/datetime/index.ts
Normal file
1
libs/react-helpers/src/lib/datetime/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './datetime';
|
12
libs/react-helpers/src/lib/decimals/index.ts
Normal file
12
libs/react-helpers/src/lib/decimals/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
|
||||
export function addDecimal(value: string, decimals: number): string {
|
||||
if (!decimals) return value;
|
||||
return new BigNumber(value || 0)
|
||||
.dividedBy(Math.pow(10, decimals))
|
||||
.toFixed(decimals);
|
||||
}
|
||||
export function removeDecimal(value: string, decimals: number): string {
|
||||
if (!decimals) return value;
|
||||
return new BigNumber(value || 0).times(Math.pow(10, decimals)).toFixed(0);
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
export const Chart = () => <div>TODO: Chart</div>;
|
||||
export const Ticket = () => <div>TODO: Ticket</div>;
|
||||
export const Orderbook = () => <div>TODO: Orderbook</div>;
|
||||
export const Orders = () => <div>TODO: Orders</div>;
|
||||
export const Positions = () => <div>TODO: Positions</div>;
|
||||
export const Collateral = () => <div>TODO: Collateral</div>;
|
||||
|
||||
export type TradingView = keyof typeof TradingViews;
|
||||
|
||||
export const TradingViews = {
|
||||
chart: Chart,
|
||||
ticket: Ticket,
|
||||
orderbook: Orderbook,
|
||||
orders: Orders,
|
||||
positions: Positions,
|
||||
collateral: Collateral,
|
||||
};
|
@ -141,11 +141,9 @@ module.exports = {
|
||||
'ui-small': ['10px', '16px'],
|
||||
},
|
||||
|
||||
extend: {
|
||||
boxShadow: {
|
||||
callout: '5px 5px 0 1px rgba(255, 255, 255, 0.05)',
|
||||
focus: '0px 0px 0px 1px #FFFFFF, 0px 0px 3px 2px #FFE600',
|
||||
'focus-dark': '0px 0px 0px 1px #000000, 0px 0px 3px 2px #FFE600',
|
||||
},
|
||||
boxShadow: {
|
||||
callout: '5px 5px 0 1px rgba(255, 255, 255, 0.05)',
|
||||
focus: '0px 0px 0px 1px #FFFFFF, 0px 0px 3px 2px #FFE600',
|
||||
'focus-dark': '0px 0px 0px 1px #000000, 0px 0px 3px 2px #FFE600',
|
||||
},
|
||||
};
|
||||
|
@ -18,12 +18,22 @@ export const decorators = [
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-body">
|
||||
<div className="dark bg-black p-16">
|
||||
<StoryWrapper className="dark bg-black">
|
||||
<Story />
|
||||
</div>
|
||||
<div className="p-16">
|
||||
</StoryWrapper>
|
||||
<StoryWrapper>
|
||||
<Story />
|
||||
</div>
|
||||
</StoryWrapper>
|
||||
</div>
|
||||
),
|
||||
];
|
||||
|
||||
const StoryWrapper = ({ children, className }) => (
|
||||
<div className={className}>
|
||||
<div className="p-16">
|
||||
<div className="dark:bg-black dark:text-white-60 bg-white text-black-60">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -128,6 +128,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
{
|
||||
variant = 'primary',
|
||||
type = 'button',
|
||||
children,
|
||||
className,
|
||||
prependIconName,
|
||||
@ -137,7 +138,12 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<button ref={ref} className={getClassName(className, variant)} {...props}>
|
||||
<button
|
||||
ref={ref}
|
||||
className={getClassName(className, variant)}
|
||||
type={type}
|
||||
{...props}
|
||||
>
|
||||
{getContent(children, prependIconName, appendIconName)}
|
||||
</button>
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Callout } from './callout';
|
||||
import { Button } from '../button';
|
||||
import { Intent } from '../../utils/intent';
|
||||
|
||||
export default {
|
||||
title: 'Callout',
|
||||
@ -20,43 +21,43 @@ Default.args = {
|
||||
|
||||
export const Danger = Template.bind({});
|
||||
Danger.args = {
|
||||
intent: 'danger',
|
||||
intent: Intent.Danger,
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Warning = Template.bind({});
|
||||
Warning.args = {
|
||||
intent: 'warning',
|
||||
intent: Intent.Warning,
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Prompt = Template.bind({});
|
||||
Prompt.args = {
|
||||
intent: 'prompt',
|
||||
intent: Intent.Prompt,
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Progress = Template.bind({});
|
||||
Progress.args = {
|
||||
intent: 'progress',
|
||||
intent: Intent.Progress,
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Success = Template.bind({});
|
||||
Success.args = {
|
||||
intent: 'success',
|
||||
intent: Intent.Success,
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const Help = Template.bind({});
|
||||
Help.args = {
|
||||
intent: 'help',
|
||||
intent: Intent.Help,
|
||||
children: 'Content',
|
||||
};
|
||||
|
||||
export const IconAndContent = Template.bind({});
|
||||
IconAndContent.args = {
|
||||
intent: 'help',
|
||||
intent: Intent.Help,
|
||||
title: 'This is what this thing does',
|
||||
iconName: 'endorsed',
|
||||
children: (
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { Callout } from '.';
|
||||
import { Intent } from '../../utils/intent';
|
||||
|
||||
test('It renders content within callout', () => {
|
||||
render(<Callout>Content</Callout>);
|
||||
@ -15,25 +16,17 @@ test('It renders title and icon', () => {
|
||||
expect(screen.getByText('title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const intents = ['danger', 'warning', 'prompt', 'success', 'help'] as [
|
||||
'danger',
|
||||
'warning',
|
||||
'prompt',
|
||||
'success',
|
||||
'help'
|
||||
];
|
||||
const intents = Object.values(Intent).filter((i) => i !== Intent.Progress);
|
||||
|
||||
intents.map((intent) =>
|
||||
test(`Applies class for ${intent}`, () => {
|
||||
render(<Callout intent={intent} />);
|
||||
expect(screen.getByTestId('callout')).toHaveClass(
|
||||
`shadow-intent-${intent}`
|
||||
);
|
||||
})
|
||||
);
|
||||
test.each(intents)('Applies class for %s', (intent) => {
|
||||
render(<Callout intent={intent} />);
|
||||
expect(screen.getByTestId('callout')).toHaveClass(
|
||||
`shadow-intent-${intent.toLowerCase()}`
|
||||
);
|
||||
});
|
||||
|
||||
test(`Applies class for progress`, () => {
|
||||
render(<Callout intent="progress" />);
|
||||
render(<Callout intent={Intent.Progress} />);
|
||||
expect(screen.getByTestId('callout')).toHaveClass(
|
||||
'shadow-black',
|
||||
'dark:shadow-white'
|
||||
|
@ -1,10 +1,11 @@
|
||||
import classNames from 'classnames';
|
||||
import { getIntentShadow, Intent } from '../../utils/intent';
|
||||
import { Icon, IconName } from '../icon';
|
||||
|
||||
export interface CalloutProps {
|
||||
children?: React.ReactNode;
|
||||
title?: React.ReactElement | string;
|
||||
intent?: 'danger' | 'warning' | 'prompt' | 'progress' | 'success' | 'help';
|
||||
intent?: Intent;
|
||||
iconName?: IconName;
|
||||
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
||||
}
|
||||
@ -12,25 +13,19 @@ export interface CalloutProps {
|
||||
export function Callout({
|
||||
children,
|
||||
title,
|
||||
intent = 'help',
|
||||
intent = Intent.Help,
|
||||
iconName,
|
||||
headingLevel,
|
||||
}: CalloutProps) {
|
||||
const className = classNames(
|
||||
'shadow-callout',
|
||||
'border',
|
||||
'border-black',
|
||||
'dark:border-white',
|
||||
'text-body-large',
|
||||
'dark:text-white',
|
||||
'p-8',
|
||||
getIntentShadow(intent),
|
||||
{
|
||||
'shadow-intent-danger': intent === 'danger',
|
||||
'shadow-intent-warning': intent === 'warning',
|
||||
'shadow-intent-prompt': intent === 'prompt',
|
||||
'shadow-black dark:shadow-white': intent === 'progress',
|
||||
'shadow-intent-success': intent === 'success',
|
||||
'shadow-intent-help': intent === 'help',
|
||||
flex: !!iconName,
|
||||
}
|
||||
);
|
||||
|
56
libs/ui-toolkit/src/components/dialog/dialog.stories.tsx
Normal file
56
libs/ui-toolkit/src/components/dialog/dialog.stories.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Dialog } from './dialog';
|
||||
import { Button } from '../button';
|
||||
import { Intent } from '../../utils/intent';
|
||||
|
||||
export default {
|
||||
title: 'Dialog',
|
||||
component: Dialog,
|
||||
} as ComponentMeta<typeof Dialog>;
|
||||
|
||||
const Template: ComponentStory<typeof Dialog> = (args) => {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => setOpen(true)}>Open dialog</Button>
|
||||
<Dialog {...args} open={open} setOpen={setOpen} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
open: false,
|
||||
title: 'Title',
|
||||
setOpen: () => undefined,
|
||||
children: <p>Some content</p>,
|
||||
};
|
||||
|
||||
export const Danger = Template.bind({});
|
||||
Danger.args = {
|
||||
open: false,
|
||||
title: 'Danger',
|
||||
setOpen: () => undefined,
|
||||
children: <p>Some content</p>,
|
||||
intent: Intent.Danger,
|
||||
};
|
||||
|
||||
export const Success = Template.bind({});
|
||||
Success.args = {
|
||||
open: false,
|
||||
title: 'Success',
|
||||
setOpen: () => undefined,
|
||||
children: <p>Some content</p>,
|
||||
intent: Intent.Success,
|
||||
};
|
||||
|
||||
export const Warning = Template.bind({});
|
||||
Warning.args = {
|
||||
open: false,
|
||||
title: 'Warning',
|
||||
setOpen: () => undefined,
|
||||
children: <p>Some content</p>,
|
||||
intent: Intent.Warning,
|
||||
};
|
43
libs/ui-toolkit/src/components/dialog/dialog.tsx
Normal file
43
libs/ui-toolkit/src/components/dialog/dialog.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import * as DialogPrimitives from '@radix-ui/react-dialog';
|
||||
import classNames from 'classnames';
|
||||
import { ReactNode } from 'react';
|
||||
import { getIntentShadow, Intent } from '../../utils/intent';
|
||||
import { Icon } from '../icon';
|
||||
|
||||
interface DialogProps {
|
||||
children: ReactNode;
|
||||
open: boolean;
|
||||
onChange: (isOpen: boolean) => void;
|
||||
title?: string;
|
||||
intent?: Intent;
|
||||
}
|
||||
|
||||
export function Dialog({
|
||||
children,
|
||||
open,
|
||||
onChange,
|
||||
title,
|
||||
intent,
|
||||
}: DialogProps) {
|
||||
const contentClasses = classNames(
|
||||
// Positions the modal in the center of screen
|
||||
'fixed 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)
|
||||
);
|
||||
return (
|
||||
<DialogPrimitives.Root open={open} onOpenChange={(x) => onChange(x)}>
|
||||
<DialogPrimitives.Portal>
|
||||
<DialogPrimitives.Overlay className="fixed inset-0 bg-black/50 dark:bg-white/15" />
|
||||
<DialogPrimitives.Content className={contentClasses}>
|
||||
<DialogPrimitives.Close className="p-12 absolute top-0 right-0">
|
||||
<Icon name="cross" />
|
||||
</DialogPrimitives.Close>
|
||||
{title && <h1 className="text-h5 mb-12">{title}</h1>}
|
||||
{children}
|
||||
</DialogPrimitives.Content>
|
||||
</DialogPrimitives.Portal>
|
||||
</DialogPrimitives.Root>
|
||||
);
|
||||
}
|
1
libs/ui-toolkit/src/components/dialog/index.ts
Normal file
1
libs/ui-toolkit/src/components/dialog/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './dialog';
|
@ -1,23 +0,0 @@
|
||||
import * as DialogPrimitives from '@radix-ui/react-dialog';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface DialogProps {
|
||||
children: ReactNode;
|
||||
open: boolean;
|
||||
setOpen: (isOpen: boolean) => void;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export function Dialog({ children, open, setOpen, title }: DialogProps) {
|
||||
return (
|
||||
<DialogPrimitives.Root open={open} onOpenChange={(x) => setOpen(x)}>
|
||||
<DialogPrimitives.Portal>
|
||||
<DialogPrimitives.Overlay className="fixed inset-0 bg-black/40 dark:bg-white/15" />
|
||||
<DialogPrimitives.Content className="fixed w-[500px] p-28 dark:bg-black dark:text-white-60 bg-white text-black-60 top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]">
|
||||
{title && <h1 className="text-h5 mb-12">{title}</h1>}
|
||||
{children}
|
||||
</DialogPrimitives.Content>
|
||||
</DialogPrimitives.Portal>
|
||||
</DialogPrimitives.Root>
|
||||
);
|
||||
}
|
@ -1,16 +1,26 @@
|
||||
import classNames from 'classnames';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface FormGroupProps {
|
||||
children: ReactNode;
|
||||
label: string;
|
||||
labelFor: string;
|
||||
label?: string;
|
||||
labelFor?: string;
|
||||
labelAlign?: 'left' | 'right';
|
||||
}
|
||||
|
||||
export const FormGroup = ({ children, label, labelFor }: FormGroupProps) => {
|
||||
export const FormGroup = ({
|
||||
children,
|
||||
label,
|
||||
labelFor,
|
||||
labelAlign = 'left',
|
||||
}: FormGroupProps) => {
|
||||
const labelClasses = classNames('block text-ui mb-4', {
|
||||
'text-right': labelAlign === 'right',
|
||||
});
|
||||
return (
|
||||
<div className="mb-12">
|
||||
<div className="mb-20">
|
||||
{label && (
|
||||
<label className="block text-ui" htmlFor={labelFor}>
|
||||
<label className={labelClasses} htmlFor={labelFor}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
|
1
libs/ui-toolkit/src/components/loader/index.ts
Normal file
1
libs/ui-toolkit/src/components/loader/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './loader';
|
29
libs/ui-toolkit/src/components/loader/loader.tsx
Normal file
29
libs/ui-toolkit/src/components/loader/loader.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const Loader = () => {
|
||||
const [, forceRender] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
forceRender((x) => !x);
|
||||
}, 100);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<span className="flex flex-wrap w-[15px] h-[15px]">
|
||||
{new Array(9).fill(null).map((_, i) => {
|
||||
return (
|
||||
<span
|
||||
key={i}
|
||||
className="block w-[5px] h-[5px] bg-black dark:bg-white"
|
||||
style={{
|
||||
opacity: Math.random() > 0.5 ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
};
|
1
libs/ui-toolkit/src/components/splash/index.ts
Normal file
1
libs/ui-toolkit/src/components/splash/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './splash';
|
14
libs/ui-toolkit/src/components/splash/splash.stories.tsx
Normal file
14
libs/ui-toolkit/src/components/splash/splash.stories.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { Story, ComponentMeta } from '@storybook/react';
|
||||
import { Splash, SplashProps } from './splash';
|
||||
|
||||
export default {
|
||||
component: Splash,
|
||||
title: 'Splash',
|
||||
} as ComponentMeta<typeof Splash>;
|
||||
|
||||
const Template: Story<SplashProps> = (args) => <Splash {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
children: <>Some center text to be used as splash screen content!</>,
|
||||
};
|
14
libs/ui-toolkit/src/components/splash/splash.tsx
Normal file
14
libs/ui-toolkit/src/components/splash/splash.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import classNames from 'classnames';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface SplashProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const Splash = ({ children }: SplashProps) => {
|
||||
const splashClasses = classNames(
|
||||
'w-full h-full',
|
||||
'flex items-center justify-center'
|
||||
);
|
||||
return <div className={splashClasses}>{children}</div>;
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
import * as EthereumUtils from './utils/web3';
|
||||
|
||||
// Components
|
||||
export { AgGridLazy, AgGridDynamic } from './components/ag-grid';
|
||||
export { Button, AnchorButton } from './components/button';
|
||||
export { Callout } from './components/callout';
|
||||
@ -9,7 +10,12 @@ export { FormGroup } from './components/form-group';
|
||||
export { Icon } from './components/icon';
|
||||
export { Input } from './components/input';
|
||||
export { InputError } from './components/input-error';
|
||||
export { Loader } from './components/loader';
|
||||
export { Select } from './components/select';
|
||||
export { Splash } from './components/splash';
|
||||
export { TextArea } from './components/text-area';
|
||||
export { ThemeSwitcher } from './components/theme-switcher';
|
||||
export { Dialog } from './components/dialog';
|
||||
export { Dialog } from './components/dialog/dialog';
|
||||
|
||||
// Utils
|
||||
export * from './utils/intent';
|
||||
|
21
libs/ui-toolkit/src/utils/intent.ts
Normal file
21
libs/ui-toolkit/src/utils/intent.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import classNames from 'classnames';
|
||||
|
||||
export enum Intent {
|
||||
Danger = 'danger',
|
||||
Warning = 'warning',
|
||||
Prompt = 'prompt',
|
||||
Progress = 'progress',
|
||||
Success = 'success',
|
||||
Help = 'help',
|
||||
}
|
||||
|
||||
export const getIntentShadow = (intent?: Intent) => {
|
||||
return classNames('shadow-callout', {
|
||||
'shadow-intent-danger': intent === Intent.Danger,
|
||||
'shadow-intent-warning': intent === Intent.Warning,
|
||||
'shadow-intent-prompt': intent === Intent.Prompt,
|
||||
'shadow-black dark:shadow-white': intent === Intent.Progress,
|
||||
'shadow-intent-success': intent === Intent.Success,
|
||||
'shadow-intent-help': intent === Intent.Help,
|
||||
});
|
||||
};
|
@ -47,7 +47,7 @@ export function VegaConnectDialog({
|
||||
return (
|
||||
<Dialog
|
||||
open={dialogOpen}
|
||||
setOpen={setDialogOpen}
|
||||
onChange={setDialogOpen}
|
||||
title="Connect to your Vega Wallet"
|
||||
>
|
||||
{selectedConnector instanceof RestConnector ? (
|
||||
|
@ -5,6 +5,14 @@ import {
|
||||
} from '@vegaprotocol/vegawallet-service-api-client';
|
||||
export { RestConnector } from './rest-connector';
|
||||
|
||||
type ErrorResponse =
|
||||
| {
|
||||
error: string;
|
||||
}
|
||||
| {
|
||||
errors: object;
|
||||
};
|
||||
|
||||
export interface VegaConnector {
|
||||
/** Description of how to use this connector */
|
||||
description: string;
|
||||
@ -16,5 +24,7 @@ export interface VegaConnector {
|
||||
disconnect(): Promise<void>;
|
||||
|
||||
/** Send a TX to the network. Only support order submission for now */
|
||||
sendTx: (body: OrderSubmissionBody) => Promise<TransactionResponse | null>;
|
||||
sendTx: (
|
||||
body: OrderSubmissionBody
|
||||
) => Promise<TransactionResponse | ErrorResponse>;
|
||||
}
|
||||
|
@ -86,7 +86,31 @@ export class RestConnector implements VegaConnector {
|
||||
}
|
||||
|
||||
async sendTx(body: OrderSubmissionBody) {
|
||||
return await this.service.commandSyncPost(body);
|
||||
try {
|
||||
const res = await this.service.commandSyncPost(body);
|
||||
return res;
|
||||
} catch (err) {
|
||||
return this.handleSendTxError(err);
|
||||
}
|
||||
}
|
||||
|
||||
private handleSendTxError(err: unknown) {
|
||||
if (typeof err === 'object' && err && 'body' in err) {
|
||||
try {
|
||||
// @ts-ignore Not sure why TS can't infer that 'body' does indeed exist on object
|
||||
const parsedError = JSON.parse(err.body);
|
||||
return parsedError;
|
||||
} catch {
|
||||
// Unexpected response
|
||||
return {
|
||||
error: 'Something went wrong',
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
error: 'Something went wrong',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private setConfig(cfg: RestConnectorConfig) {
|
||||
|
@ -1,10 +1,18 @@
|
||||
import {
|
||||
VegaKey,
|
||||
OrderSubmissionBody,
|
||||
TransactionResponse,
|
||||
} from '@vegaprotocol/vegawallet-service-api-client';
|
||||
import { createContext } from 'react';
|
||||
import { VegaConnector } from './connectors';
|
||||
import { Transaction } from './types';
|
||||
|
||||
export type SendTxError =
|
||||
| {
|
||||
error: string;
|
||||
}
|
||||
| {
|
||||
errors: object;
|
||||
};
|
||||
|
||||
export interface VegaKeyExtended extends VegaKey {
|
||||
name: string;
|
||||
@ -30,7 +38,9 @@ export interface VegaWalletContextShape {
|
||||
connector: VegaConnector | null;
|
||||
|
||||
/** Send a transaction to the network, only order submissions for now */
|
||||
sendTx: (body: OrderSubmissionBody) => Promise<TransactionResponse | null>;
|
||||
sendTx: (
|
||||
tx: Transaction
|
||||
) => Promise<TransactionResponse | SendTxError> | null;
|
||||
}
|
||||
|
||||
export const VegaWalletContext = createContext<
|
||||
|
@ -4,3 +4,4 @@ export * from './hooks';
|
||||
export * from './connect-dialog';
|
||||
export * from './connectors';
|
||||
export * from './storage-keys';
|
||||
export * from './types';
|
||||
|
@ -50,7 +50,7 @@ const generateJSX = () => (
|
||||
</VegaWalletProvider>
|
||||
);
|
||||
|
||||
test('Renders children', async () => {
|
||||
test('Can connect, disconnect and retrieve keypairs', async () => {
|
||||
const mockKeypairs = [{ pub: 'public key 1' }, { pub: 'public key 2' }];
|
||||
localStorage.setItem(WALLET_KEY, mockKeypairs[0].pub);
|
||||
jest
|
||||
|
@ -66,17 +66,12 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const sendTx = useCallback(async (body: OrderSubmissionBody) => {
|
||||
const sendTx = useCallback((body: OrderSubmissionBody) => {
|
||||
if (!connector.current) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return connector.current.sendTx(body);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
return connector.current.sendTx(body);
|
||||
}, []);
|
||||
|
||||
// Current selected keypair derived from publicKey state
|
||||
|
36
libs/wallet/src/types.ts
Normal file
36
libs/wallet/src/types.ts
Normal file
@ -0,0 +1,36 @@
|
||||
export enum OrderType {
|
||||
Market = 'TYPE_MARKET',
|
||||
Limit = 'TYPE_LIMIT',
|
||||
}
|
||||
|
||||
export enum OrderSide {
|
||||
Buy = 'SIDE_BUY',
|
||||
Sell = 'SIDE_SELL',
|
||||
}
|
||||
|
||||
export enum OrderTimeInForce {
|
||||
GTC = 'TIME_IN_FORCE_GTC',
|
||||
GTT = 'TIME_IN_FORCE_GTT',
|
||||
IOC = 'TIME_IN_FORCE_IOC',
|
||||
FOK = 'TIME_IN_FORCE_FOK',
|
||||
GFN = 'TIME_IN_FORCE_GFN',
|
||||
GFA = 'TIME_IN_FORCE_GFA',
|
||||
}
|
||||
|
||||
export interface OrderSubmission {
|
||||
pubKey: string;
|
||||
propagate: boolean;
|
||||
orderSubmission: {
|
||||
marketId: string;
|
||||
size: string;
|
||||
type: OrderType;
|
||||
side: OrderSide;
|
||||
timeInForce: OrderTimeInForce;
|
||||
price?: string;
|
||||
expiresAt?: string;
|
||||
reference?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Will make Transaction a union type as other transactions are added
|
||||
export type Transaction = OrderSubmission;
|
@ -26,9 +26,11 @@
|
||||
"ag-grid-react": "^27.0.1",
|
||||
"apollo": "^2.33.9",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"bignumber.js": "^9.0.2",
|
||||
"classnames": "^2.3.1",
|
||||
"core-js": "^3.6.5",
|
||||
"env-cmd": "^10.1.0",
|
||||
"ethers": "^5.6.0",
|
||||
"graphql": "^15.7.2",
|
||||
"graphql-ws": "^5.6.3",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
@ -42,6 +44,7 @@
|
||||
"react-use-websocket": "^3.0.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.6",
|
||||
"regenerator-runtime": "0.13.7",
|
||||
"sha3": "^2.1.4",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"tslib": "^2.0.0",
|
||||
"uuid": "^8.3.2"
|
||||
|
@ -15,6 +15,8 @@
|
||||
"skipDefaultLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@vegaprotocol/deal-ticket": ["libs/deal-ticket/src/index.ts"],
|
||||
"@vegaprotocol/graphql": ["libs/graphql/src/index.ts"],
|
||||
"@vegaprotocol/react-helpers": ["libs/react-helpers/src/index.ts"],
|
||||
"@vegaprotocol/tailwindcss-config": [
|
||||
"libs/tailwindcss-config/src/index.js"
|
||||
|
@ -1,8 +1,10 @@
|
||||
{
|
||||
"version": 2,
|
||||
"projects": {
|
||||
"deal-ticket": "libs/deal-ticket",
|
||||
"explorer": "apps/explorer",
|
||||
"explorer-e2e": "apps/explorer-e2e",
|
||||
"graphql": "libs/graphql",
|
||||
"react-helpers": "libs/react-helpers",
|
||||
"tailwindcss-config": "libs/tailwindcss-config",
|
||||
"trading": "apps/trading",
|
||||
|
438
yarn.lock
438
yarn.lock
@ -1496,6 +1496,346 @@
|
||||
minimatch "^3.0.4"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@ethersproject/abi@5.6.0", "@ethersproject/abi@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.6.0.tgz#ea07cbc1eec2374d32485679c12408005895e9f3"
|
||||
integrity sha512-AhVByTwdXCc2YQ20v300w6KVHle9g2OFc28ZAFCPnJyEpkv1xKXjZcSTgWOlv1i+0dqlgF8RCF2Rn2KC1t+1Vg==
|
||||
dependencies:
|
||||
"@ethersproject/address" "^5.6.0"
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/constants" "^5.6.0"
|
||||
"@ethersproject/hash" "^5.6.0"
|
||||
"@ethersproject/keccak256" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/strings" "^5.6.0"
|
||||
|
||||
"@ethersproject/abstract-provider@5.6.0", "@ethersproject/abstract-provider@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.6.0.tgz#0c4ac7054650dbd9c476cf5907f588bbb6ef3061"
|
||||
integrity sha512-oPMFlKLN+g+y7a79cLK3WiLcjWFnZQtXWgnLAbHZcN3s7L4v90UHpTOrLk+m3yr0gt+/h9STTM6zrr7PM8uoRw==
|
||||
dependencies:
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/networks" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/transactions" "^5.6.0"
|
||||
"@ethersproject/web" "^5.6.0"
|
||||
|
||||
"@ethersproject/abstract-signer@5.6.0", "@ethersproject/abstract-signer@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.6.0.tgz#9cd7ae9211c2b123a3b29bf47aab17d4d016e3e7"
|
||||
integrity sha512-WOqnG0NJKtI8n0wWZPReHtaLkDByPL67tn4nBaDAhmVq8sjHTPbCdz4DRhVu/cfTOvfy9w3iq5QZ7BX7zw56BQ==
|
||||
dependencies:
|
||||
"@ethersproject/abstract-provider" "^5.6.0"
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
|
||||
"@ethersproject/address@5.6.0", "@ethersproject/address@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.6.0.tgz#13c49836d73e7885fc148ad633afad729da25012"
|
||||
integrity sha512-6nvhYXjbXsHPS+30sHZ+U4VMagFC/9zAk6Gd/h3S21YW4+yfb0WfRtaAIZ4kfM4rrVwqiy284LP0GtL5HXGLxQ==
|
||||
dependencies:
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/keccak256" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/rlp" "^5.6.0"
|
||||
|
||||
"@ethersproject/base64@5.6.0", "@ethersproject/base64@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.6.0.tgz#a12c4da2a6fb86d88563216b0282308fc15907c9"
|
||||
integrity sha512-2Neq8wxJ9xHxCF9TUgmKeSh9BXJ6OAxWfeGWvbauPh8FuHEjamgHilllx8KkSd5ErxyHIX7Xv3Fkcud2kY9ezw==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
|
||||
"@ethersproject/basex@5.6.0", "@ethersproject/basex@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.6.0.tgz#9ea7209bf0a1c3ddc2a90f180c3a7f0d7d2e8a69"
|
||||
integrity sha512-qN4T+hQd/Md32MoJpc69rOwLYRUXwjTlhHDIeUkUmiN/JyWkkLLMoG0TqvSQKNqZOMgN5stbUYN6ILC+eD7MEQ==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
|
||||
"@ethersproject/bignumber@5.6.0", "@ethersproject/bignumber@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.6.0.tgz#116c81b075c57fa765a8f3822648cf718a8a0e26"
|
||||
integrity sha512-VziMaXIUHQlHJmkv1dlcd6GY2PmT0khtAqaMctCIDogxkrarMzA9L94KN1NeXqqOfFD6r0sJT3vCTOFSmZ07DA==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
bn.js "^4.11.9"
|
||||
|
||||
"@ethersproject/bytes@5.6.0", "@ethersproject/bytes@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.6.0.tgz#81652f2a0e04533575befadce555213c11d8aa20"
|
||||
integrity sha512-3hJPlYemb9V4VLfJF5BfN0+55vltPZSHU3QKUyP9M3Y2TcajbiRrz65UG+xVHOzBereB1b9mn7r12o177xgN7w==
|
||||
dependencies:
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
|
||||
"@ethersproject/constants@5.6.0", "@ethersproject/constants@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.6.0.tgz#55e3eb0918584d3acc0688e9958b0cedef297088"
|
||||
integrity sha512-SrdaJx2bK0WQl23nSpV/b1aq293Lh0sUaZT/yYKPDKn4tlAbkH96SPJwIhwSwTsoQQZxuh1jnqsKwyymoiBdWA==
|
||||
dependencies:
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
|
||||
"@ethersproject/contracts@5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.6.0.tgz#60f2cfc7addd99a865c6c8cfbbcec76297386067"
|
||||
integrity sha512-74Ge7iqTDom0NX+mux8KbRUeJgu1eHZ3iv6utv++sLJG80FVuU9HnHeKVPfjd9s3woFhaFoQGf3B3iH/FrQmgw==
|
||||
dependencies:
|
||||
"@ethersproject/abi" "^5.6.0"
|
||||
"@ethersproject/abstract-provider" "^5.6.0"
|
||||
"@ethersproject/abstract-signer" "^5.6.0"
|
||||
"@ethersproject/address" "^5.6.0"
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/constants" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/transactions" "^5.6.0"
|
||||
|
||||
"@ethersproject/hash@5.6.0", "@ethersproject/hash@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.6.0.tgz#d24446a5263e02492f9808baa99b6e2b4c3429a2"
|
||||
integrity sha512-fFd+k9gtczqlr0/BruWLAu7UAOas1uRRJvOR84uDf4lNZ+bTkGl366qvniUZHKtlqxBRU65MkOobkmvmpHU+jA==
|
||||
dependencies:
|
||||
"@ethersproject/abstract-signer" "^5.6.0"
|
||||
"@ethersproject/address" "^5.6.0"
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/keccak256" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/strings" "^5.6.0"
|
||||
|
||||
"@ethersproject/hdnode@5.6.0", "@ethersproject/hdnode@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.6.0.tgz#9dcbe8d629bbbcf144f2cae476337fe92d320998"
|
||||
integrity sha512-61g3Jp3nwDqJcL/p4nugSyLrpl/+ChXIOtCEM8UDmWeB3JCAt5FoLdOMXQc3WWkc0oM2C0aAn6GFqqMcS/mHTw==
|
||||
dependencies:
|
||||
"@ethersproject/abstract-signer" "^5.6.0"
|
||||
"@ethersproject/basex" "^5.6.0"
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/pbkdf2" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/sha2" "^5.6.0"
|
||||
"@ethersproject/signing-key" "^5.6.0"
|
||||
"@ethersproject/strings" "^5.6.0"
|
||||
"@ethersproject/transactions" "^5.6.0"
|
||||
"@ethersproject/wordlists" "^5.6.0"
|
||||
|
||||
"@ethersproject/json-wallets@5.6.0", "@ethersproject/json-wallets@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.6.0.tgz#4c2fc27f17e36c583e7a252fb938bc46f98891e5"
|
||||
integrity sha512-fmh86jViB9r0ibWXTQipxpAGMiuxoqUf78oqJDlCAJXgnJF024hOOX7qVgqsjtbeoxmcLwpPsXNU0WEe/16qPQ==
|
||||
dependencies:
|
||||
"@ethersproject/abstract-signer" "^5.6.0"
|
||||
"@ethersproject/address" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/hdnode" "^5.6.0"
|
||||
"@ethersproject/keccak256" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/pbkdf2" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/random" "^5.6.0"
|
||||
"@ethersproject/strings" "^5.6.0"
|
||||
"@ethersproject/transactions" "^5.6.0"
|
||||
aes-js "3.0.0"
|
||||
scrypt-js "3.0.1"
|
||||
|
||||
"@ethersproject/keccak256@5.6.0", "@ethersproject/keccak256@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.6.0.tgz#fea4bb47dbf8f131c2e1774a1cecbfeb9d606459"
|
||||
integrity sha512-tk56BJ96mdj/ksi7HWZVWGjCq0WVl/QvfhFQNeL8fxhBlGoP+L80uDCiQcpJPd+2XxkivS3lwRm3E0CXTfol0w==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
js-sha3 "0.8.0"
|
||||
|
||||
"@ethersproject/logger@5.6.0", "@ethersproject/logger@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.6.0.tgz#d7db1bfcc22fd2e4ab574cba0bb6ad779a9a3e7a"
|
||||
integrity sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg==
|
||||
|
||||
"@ethersproject/networks@5.6.0", "@ethersproject/networks@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.6.0.tgz#486d03fff29b4b6b5414d47a232ded09fe10de5e"
|
||||
integrity sha512-DaVzgyThzHgSDLuURhvkp4oviGoGe9iTZW4jMEORHDRCgSZ9K9THGFKqL+qGXqPAYLEgZTf5z2w56mRrPR1MjQ==
|
||||
dependencies:
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
|
||||
"@ethersproject/pbkdf2@5.6.0", "@ethersproject/pbkdf2@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.6.0.tgz#04fcc2d7c6bff88393f5b4237d906a192426685a"
|
||||
integrity sha512-Wu1AxTgJo3T3H6MIu/eejLFok9TYoSdgwRr5oGY1LTLfmGesDoSx05pemsbrPT2gG4cQME+baTSCp5sEo2erZQ==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/sha2" "^5.6.0"
|
||||
|
||||
"@ethersproject/properties@5.6.0", "@ethersproject/properties@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.6.0.tgz#38904651713bc6bdd5bdd1b0a4287ecda920fa04"
|
||||
integrity sha512-szoOkHskajKePTJSZ46uHUWWkbv7TzP2ypdEK6jGMqJaEt2sb0jCgfBo0gH0m2HBpRixMuJ6TBRaQCF7a9DoCg==
|
||||
dependencies:
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
|
||||
"@ethersproject/providers@5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.6.0.tgz#08ec8e2666771e3a347e66c8f664a2af97366534"
|
||||
integrity sha512-6+5PKXTWAttJWFWF8+xCDTCa2/dtq9BNrdKQHGl0IyIOwj99vM6OeThmIRcsIAzIOb8m0XS6w+1KFZwrf3j9nw==
|
||||
dependencies:
|
||||
"@ethersproject/abstract-provider" "^5.6.0"
|
||||
"@ethersproject/abstract-signer" "^5.6.0"
|
||||
"@ethersproject/address" "^5.6.0"
|
||||
"@ethersproject/basex" "^5.6.0"
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/constants" "^5.6.0"
|
||||
"@ethersproject/hash" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/networks" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/random" "^5.6.0"
|
||||
"@ethersproject/rlp" "^5.6.0"
|
||||
"@ethersproject/sha2" "^5.6.0"
|
||||
"@ethersproject/strings" "^5.6.0"
|
||||
"@ethersproject/transactions" "^5.6.0"
|
||||
"@ethersproject/web" "^5.6.0"
|
||||
bech32 "1.1.4"
|
||||
ws "7.4.6"
|
||||
|
||||
"@ethersproject/random@5.6.0", "@ethersproject/random@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.6.0.tgz#1505d1ab6a250e0ee92f436850fa3314b2cb5ae6"
|
||||
integrity sha512-si0PLcLjq+NG/XHSZz90asNf+YfKEqJGVdxoEkSukzbnBgC8rydbgbUgBbBGLeHN4kAJwUFEKsu3sCXT93YMsw==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
|
||||
"@ethersproject/rlp@5.6.0", "@ethersproject/rlp@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.6.0.tgz#55a7be01c6f5e64d6e6e7edb6061aa120962a717"
|
||||
integrity sha512-dz9WR1xpcTL+9DtOT/aDO+YyxSSdO8YIS0jyZwHHSlAmnxA6cKU3TrTd4Xc/bHayctxTgGLYNuVVoiXE4tTq1g==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
|
||||
"@ethersproject/sha2@5.6.0", "@ethersproject/sha2@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.6.0.tgz#364c4c11cc753bda36f31f001628706ebadb64d9"
|
||||
integrity sha512-1tNWCPFLu1n3JM9t4/kytz35DkuF9MxqkGGEHNauEbaARdm2fafnOyw1s0tIQDPKF/7bkP1u3dbrmjpn5CelyA==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
hash.js "1.1.7"
|
||||
|
||||
"@ethersproject/signing-key@5.6.0", "@ethersproject/signing-key@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.6.0.tgz#4f02e3fb09e22b71e2e1d6dc4bcb5dafa69ce042"
|
||||
integrity sha512-S+njkhowmLeUu/r7ir8n78OUKx63kBdMCPssePS89So1TH4hZqnWFsThEd/GiXYp9qMxVrydf7KdM9MTGPFukA==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
bn.js "^4.11.9"
|
||||
elliptic "6.5.4"
|
||||
hash.js "1.1.7"
|
||||
|
||||
"@ethersproject/solidity@5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.6.0.tgz#64657362a596bf7f5630bdc921c07dd78df06dc3"
|
||||
integrity sha512-YwF52vTNd50kjDzqKaoNNbC/r9kMDPq3YzDWmsjFTRBcIF1y4JCQJ8gB30wsTfHbaxgxelI5BfxQSxD/PbJOww==
|
||||
dependencies:
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/keccak256" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/sha2" "^5.6.0"
|
||||
"@ethersproject/strings" "^5.6.0"
|
||||
|
||||
"@ethersproject/strings@5.6.0", "@ethersproject/strings@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.6.0.tgz#9891b26709153d996bf1303d39a7f4bc047878fd"
|
||||
integrity sha512-uv10vTtLTZqrJuqBZR862ZQjTIa724wGPWQqZrofaPI/kUsf53TBG0I0D+hQ1qyNtllbNzaW+PDPHHUI6/65Mg==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/constants" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
|
||||
"@ethersproject/transactions@5.6.0", "@ethersproject/transactions@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.6.0.tgz#4b594d73a868ef6e1529a2f8f94a785e6791ae4e"
|
||||
integrity sha512-4HX+VOhNjXHZyGzER6E/LVI2i6lf9ejYeWD6l4g50AdmimyuStKc39kvKf1bXWQMg7QNVh+uC7dYwtaZ02IXeg==
|
||||
dependencies:
|
||||
"@ethersproject/address" "^5.6.0"
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/constants" "^5.6.0"
|
||||
"@ethersproject/keccak256" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/rlp" "^5.6.0"
|
||||
"@ethersproject/signing-key" "^5.6.0"
|
||||
|
||||
"@ethersproject/units@5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.6.0.tgz#e5cbb1906988f5740254a21b9ded6bd51e826d9c"
|
||||
integrity sha512-tig9x0Qmh8qbo1w8/6tmtyrm/QQRviBh389EQ+d8fP4wDsBrJBf08oZfoiz1/uenKK9M78yAP4PoR7SsVoTjsw==
|
||||
dependencies:
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/constants" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
|
||||
"@ethersproject/wallet@5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.6.0.tgz#33d11a806d783864208f348709a5a3badac8e22a"
|
||||
integrity sha512-qMlSdOSTyp0MBeE+r7SUhr1jjDlC1zAXB8VD84hCnpijPQiSNbxr6GdiLXxpUs8UKzkDiNYYC5DRI3MZr+n+tg==
|
||||
dependencies:
|
||||
"@ethersproject/abstract-provider" "^5.6.0"
|
||||
"@ethersproject/abstract-signer" "^5.6.0"
|
||||
"@ethersproject/address" "^5.6.0"
|
||||
"@ethersproject/bignumber" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/hash" "^5.6.0"
|
||||
"@ethersproject/hdnode" "^5.6.0"
|
||||
"@ethersproject/json-wallets" "^5.6.0"
|
||||
"@ethersproject/keccak256" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/random" "^5.6.0"
|
||||
"@ethersproject/signing-key" "^5.6.0"
|
||||
"@ethersproject/transactions" "^5.6.0"
|
||||
"@ethersproject/wordlists" "^5.6.0"
|
||||
|
||||
"@ethersproject/web@5.6.0", "@ethersproject/web@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.6.0.tgz#4bf8b3cbc17055027e1a5dd3c357e37474eaaeb8"
|
||||
integrity sha512-G/XHj0hV1FxI2teHRfCGvfBUHFmU+YOSbCxlAMqJklxSa7QMiHFQfAxvwY2PFqgvdkxEKwRNr/eCjfAPEm2Ctg==
|
||||
dependencies:
|
||||
"@ethersproject/base64" "^5.6.0"
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/strings" "^5.6.0"
|
||||
|
||||
"@ethersproject/wordlists@5.6.0", "@ethersproject/wordlists@^5.6.0":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.6.0.tgz#79e62c5276e091d8575f6930ba01a29218ded032"
|
||||
integrity sha512-q0bxNBfIX3fUuAo9OmjlEYxP40IB8ABgb7HjEZCL5IKubzV3j30CWi2rqQbjTS2HfoyQbfINoKcTVWP4ejwR7Q==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" "^5.6.0"
|
||||
"@ethersproject/hash" "^5.6.0"
|
||||
"@ethersproject/logger" "^5.6.0"
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/strings" "^5.6.0"
|
||||
|
||||
"@gar/promisify@^1.0.1":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||
@ -4893,7 +5233,7 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@>=16.9.0":
|
||||
"@types/react@*":
|
||||
version "17.0.39"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.39.tgz#d0f4cde092502a6db00a1cded6e6bf2abb7633ce"
|
||||
integrity sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==
|
||||
@ -4911,6 +5251,15 @@
|
||||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/react@>=16.9.0":
|
||||
version "17.0.40"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.40.tgz#dc010cee6254d5239a138083f3799a16638e6bad"
|
||||
integrity sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/resolve@1.17.1":
|
||||
version "1.17.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
||||
@ -5589,6 +5938,11 @@ address@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
|
||||
integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==
|
||||
|
||||
aes-js@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d"
|
||||
integrity sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=
|
||||
|
||||
ag-grid-community@^27.0.1:
|
||||
version "27.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-27.0.1.tgz#7ce5c000d321ba2c22447837e793b1d8366f4cdb"
|
||||
@ -6692,6 +7046,11 @@ bcrypt-pbkdf@^1.0.0:
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
bech32@1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9"
|
||||
integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==
|
||||
|
||||
becke-ch--regex--s0-0-v1--base--pl--lib@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/becke-ch--regex--s0-0-v1--base--pl--lib/-/becke-ch--regex--s0-0-v1--base--pl--lib-1.4.0.tgz#429ceebbfa5f7e936e78d73fbdc7da7162b20e20"
|
||||
@ -6709,6 +7068,11 @@ big.js@^5.2.2:
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
|
||||
|
||||
bignumber.js@^9.0.2:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.2.tgz#71c6c6bed38de64e24a65ebe16cfcf23ae693673"
|
||||
integrity sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==
|
||||
|
||||
binary-extensions@^1.0.0:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
|
||||
@ -7096,6 +7460,14 @@ buffer@5.6.0:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
buffer@6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
|
||||
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
buffer@^4.3.0:
|
||||
version "4.9.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
|
||||
@ -9235,7 +9607,7 @@ element-resize-detector@^1.2.2:
|
||||
dependencies:
|
||||
batch-processor "1.0.0"
|
||||
|
||||
elliptic@^6.5.3:
|
||||
elliptic@6.5.4, elliptic@^6.5.3:
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
|
||||
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
|
||||
@ -9893,6 +10265,42 @@ etag@1.8.1, etag@~1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||
|
||||
ethers@^5.6.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.6.0.tgz#924eb965dc03963fad0a09ce687efdf49aca3b45"
|
||||
integrity sha512-00FP71jt6bW3ndO5DhgH9mLIZhoCGnAKFLu8qig5KmV03ubEChKf2ilB3g6fX512tTYo+tSMDJ5WpCJWdBHkBQ==
|
||||
dependencies:
|
||||
"@ethersproject/abi" "5.6.0"
|
||||
"@ethersproject/abstract-provider" "5.6.0"
|
||||
"@ethersproject/abstract-signer" "5.6.0"
|
||||
"@ethersproject/address" "5.6.0"
|
||||
"@ethersproject/base64" "5.6.0"
|
||||
"@ethersproject/basex" "5.6.0"
|
||||
"@ethersproject/bignumber" "5.6.0"
|
||||
"@ethersproject/bytes" "5.6.0"
|
||||
"@ethersproject/constants" "5.6.0"
|
||||
"@ethersproject/contracts" "5.6.0"
|
||||
"@ethersproject/hash" "5.6.0"
|
||||
"@ethersproject/hdnode" "5.6.0"
|
||||
"@ethersproject/json-wallets" "5.6.0"
|
||||
"@ethersproject/keccak256" "5.6.0"
|
||||
"@ethersproject/logger" "5.6.0"
|
||||
"@ethersproject/networks" "5.6.0"
|
||||
"@ethersproject/pbkdf2" "5.6.0"
|
||||
"@ethersproject/properties" "5.6.0"
|
||||
"@ethersproject/providers" "5.6.0"
|
||||
"@ethersproject/random" "5.6.0"
|
||||
"@ethersproject/rlp" "5.6.0"
|
||||
"@ethersproject/sha2" "5.6.0"
|
||||
"@ethersproject/signing-key" "5.6.0"
|
||||
"@ethersproject/solidity" "5.6.0"
|
||||
"@ethersproject/strings" "5.6.0"
|
||||
"@ethersproject/transactions" "5.6.0"
|
||||
"@ethersproject/units" "5.6.0"
|
||||
"@ethersproject/wallet" "5.6.0"
|
||||
"@ethersproject/web" "5.6.0"
|
||||
"@ethersproject/wordlists" "5.6.0"
|
||||
|
||||
eventemitter2@^6.4.3:
|
||||
version "6.4.5"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.5.tgz#97380f758ae24ac15df8353e0cc27f8b95644655"
|
||||
@ -11156,7 +11564,7 @@ hash-base@^3.0.0:
|
||||
readable-stream "^3.6.0"
|
||||
safe-buffer "^5.2.0"
|
||||
|
||||
hash.js@^1.0.0, hash.js@^1.0.3:
|
||||
hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
|
||||
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
|
||||
@ -11578,7 +11986,7 @@ identity-obj-proxy@3.0.0:
|
||||
dependencies:
|
||||
harmony-reflect "^1.4.6"
|
||||
|
||||
ieee754@^1.1.13, ieee754@^1.1.4:
|
||||
ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
@ -12905,6 +13313,11 @@ jest@27.2.3:
|
||||
import-local "^3.0.2"
|
||||
jest-cli "^27.2.3"
|
||||
|
||||
js-sha3@0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
|
||||
integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
|
||||
|
||||
js-string-escape@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
|
||||
@ -16841,6 +17254,11 @@ schema-utils@^4.0.0:
|
||||
ajv-formats "^2.1.1"
|
||||
ajv-keywords "^5.0.0"
|
||||
|
||||
scrypt-js@3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312"
|
||||
integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==
|
||||
|
||||
secure-compare@3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3"
|
||||
@ -17035,6 +17453,13 @@ sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8, sha.js@~2.4.4:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
sha3@^2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.4.tgz#000fac0fe7c2feac1f48a25e7a31b52a6492cc8f"
|
||||
integrity sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==
|
||||
dependencies:
|
||||
buffer "6.0.3"
|
||||
|
||||
shallow-clone@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
|
||||
@ -19642,6 +20067,11 @@ write-file-atomic@^3.0.0:
|
||||
signal-exit "^3.0.2"
|
||||
typedarray-to-buffer "^3.1.5"
|
||||
|
||||
ws@7.4.6:
|
||||
version "7.4.6"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
|
||||
integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
|
||||
|
||||
ws@^7.4.6:
|
||||
version "7.5.7"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67"
|
||||
|
Loading…
Reference in New Issue
Block a user