Compare commits

...
39 changed files with 220 additions and 1008 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
NX_VEGA_NETWORKS='{"SANDBOX":"https://sandbox.explorer.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_NETWORKS='{"STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
-20
View File
@@ -1,20 +0,0 @@
# App configuration variables
NX_TENDERMINT_URL=https://tm.be.mainnet-mirror.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
NX_VEGA_ENV=MIRROR
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
# App flags
NX_EXPLORER_ASSETS=1
NX_EXPLORER_GENESIS=1
NX_EXPLORER_GOVERNANCE=1
NX_EXPLORER_NETWORK_PARAMETERS=1
NX_EXPLORER_PARTIES=1
NX_EXPLORER_VALIDATORS=1
NX_EXPLORER_MARKETS=0
NX_EXPLORER_ORACLES=0
NX_EXPLORER_TXS_LIST=1
-12
View File
@@ -1,12 +0,0 @@
# App configuration variables
NX_VEGA_ENV=SANDBOX
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.sandbox.vega.xyz/websocket
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
-12
View File
@@ -1,12 +0,0 @@
# App configuration variables
NX_VEGA_ENV=MIRROR
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
NX_VEGA_URL=https://api.n00.mainnet-mirror.vega.xyz/graphql
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://mirror.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_DELEGATIONS_PAGINATION=50
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
-9
View File
@@ -1,9 +0,0 @@
# App configuration variables
NX_VEGA_URL=https://api.sandbox.vega.xyz/graphql
NX_VEGA_ENV=SANDBOX
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
+4 -8
View File
@@ -12,6 +12,7 @@ const TRUTHY = ['1', 'true'];
interface VegaContracts {
claimAddress: string;
lockedAddress: string;
tokenVestingAddress?: string;
}
const customClaimAddress = process.env['NX_CUSTOM_CLAIM_ADDRESS'] as string;
@@ -36,21 +37,16 @@ export const ContractAddresses: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
SANDBOX: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
TESTNET: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
MIRROR: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
VALIDATOR_TESTNET: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
// This is a fallback contract address for the validator testnet network which does not
// have a vesting contract address set and is therefore not in the ethereum config
tokenVestingAddress: '0xadFcb7f93a24F8743a8e548d74d2ecB373c92866',
},
MAINNET: {
claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3',
@@ -49,6 +49,13 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
signer = provider.getSigner();
}
const tokenVestingAddress =
config.token_vesting_contract?.address ||
ENV.addresses.tokenVestingAddress;
if (!tokenVestingAddress) {
throw new Error('No token vesting address found');
}
if (provider && config) {
const staking = new StakingBridge(
config.staking_bridge_contract.address,
@@ -63,7 +70,7 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
signer || provider
),
vesting: new TokenVesting(
config.token_vesting_contract.address,
tokenVestingAddress,
signer || provider
),
claim: new Claim(ENV.addresses.claimAddress, signer || provider),
@@ -14,6 +14,7 @@ import { TokenDetailsCirculating } from './token-details-circulating';
import { SplashLoader } from '../../../components/splash-loader';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { useContracts } from '../../../contexts/contracts/contracts-context';
import { ENV } from '../../../config';
export const TokenDetails = ({
totalSupply,
@@ -49,6 +50,9 @@ export const TokenDetails = ({
);
}
const tokenVestingContractAddress =
config.token_vesting_contract?.address || ENV.addresses.tokenVestingAddress;
return (
<div className="token-details">
<RoundedWrapper>
@@ -65,18 +69,20 @@ export const TokenDetails = ({
{token.address}
</Link>
</KeyValueTableRow>
<KeyValueTableRow>
{t('Vesting contract').toUpperCase()}
<Link
data-testid="token-contract"
title={t('View on Etherscan (opens in a new tab)')}
className="font-mono text-white text-right"
href={`${ETHERSCAN_URL}/address/${config.token_vesting_contract.address}`}
target="_blank"
>
{config.token_vesting_contract.address}
</Link>
</KeyValueTableRow>
{tokenVestingContractAddress && (
<KeyValueTableRow>
{t('Vesting contract').toUpperCase()}
<Link
data-testid="token-contract"
title={t('View on Etherscan (opens in a new tab)')}
className="font-mono text-white text-right"
href={`${ETHERSCAN_URL}/address/${tokenVestingContractAddress}`}
target="_blank"
>
{tokenVestingContractAddress}
</Link>
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('Total supply').toUpperCase()}
<span className="font-mono" data-testid="total-supply">
-5
View File
@@ -1,5 +0,0 @@
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
NX_VEGA_URL=https://api.sandbox.vega.xyz/graphql
NX_VEGA_ENV=SANDBOX
NX_VEGA_NETWORKS={\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET3\":\"https://stagnet3.token.vega.xyz\",\"STAGNET1\":\"https://stagnet1.token.vega.xyz\",\"TESTNET\":\"https://token.fairground.wtf\",\"MAINNET\":\"https://token.vega.xyz\"}
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
@@ -1,3 +0,0 @@
{
"hosts": ["https://api-n00.mainnet-mirror.vega.rocks/graphql"]
}
@@ -1,3 +0,0 @@
{
"hosts": ["https://api-n01.sandbox.vega.rocks/graphql"]
}
@@ -474,13 +474,16 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
}
if (tx.order && tx.order.rejectionReason) {
const rejectionReason = getRejectionReason(tx.order) || ' ';
const rejectionReason =
getRejectionReason(tx.order) || tx.order.rejectionReason || '';
return (
<>
<ToastHeading>{t('Order rejected')}</ToastHeading>
{rejectionReason ? (
{rejectionReason || tx.order.rejectionReason ? (
<p>
{t('Your order has been rejected because: %s', [rejectionReason])}
{t('Your order has been rejected because: %s', [
rejectionReason || tx.order.rejectionReason,
])}
</p>
) : (
<p>{t('Your order has been rejected.')}</p>
@@ -579,7 +582,7 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
if (orderRejection) {
label = t('Order rejected');
errorMessage = t('Your order has been rejected because: %s', [
orderRejection,
orderRejection || tx.order?.rejectionReason || ' ',
]);
}
if (walletError) {
@@ -646,7 +649,11 @@ export const useVegaTransactionToasts = () => {
// Transaction can be successful but the order can be rejected by the network
const intent =
tx.order && [OrderStatus.STATUS_REJECTED].includes(tx.order.status)
(tx.order &&
[OrderStatus.STATUS_REJECTED, OrderStatus.STATUS_STOPPED].includes(
tx.order.status
)) ||
tx.order?.rejectionReason
? Intent.Danger
: intentMap[tx.status];
@@ -107,7 +107,7 @@ describe('DealTicket', () => {
);
});
it('should use local storage state for initial values reduceOnly and postOnly', () => {
it('should set values for a non-persistent reduce only order and disable post only checkbox', () => {
const expectedOrder = {
marketId: market.id,
type: Schema.OrderType.TYPE_LIMIT,
@@ -115,7 +115,7 @@ describe('DealTicket', () => {
size: '0.1',
price: '300.22',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
persist: true,
persist: false,
reduceOnly: true,
postOnly: false,
};
@@ -149,6 +149,58 @@ describe('DealTicket', () => {
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
expectedOrder.price
);
expect(screen.getByTestId('post-only')).toBeDisabled();
expect(screen.getByTestId('reduce-only')).toBeEnabled();
expect(screen.getByTestId('reduce-only')).toBeChecked();
expect(screen.getByTestId('post-only')).not.toBeChecked();
});
it('should set values for a persistent post only order and disable reduce only checkbox', () => {
const expectedOrder = {
marketId: market.id,
type: Schema.OrderType.TYPE_LIMIT,
side: Schema.Side.SIDE_SELL,
size: '0.1',
price: '300.22',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
persist: true,
reduceOnly: false,
postOnly: true,
};
useOrderStore.setState({
orders: {
[expectedOrder.marketId]: expectedOrder,
},
});
render(generateJsx());
// Assert correct defaults are used from store
expect(
screen
.getByTestId(`order-type-${Schema.OrderType.TYPE_LIMIT}`)
.querySelector('input')
).toBeChecked();
expect(
screen.queryByTestId('order-side-SIDE_SELL')?.querySelector('input')
).toBeChecked();
expect(
screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input')
).not.toBeChecked();
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
expectedOrder.size
);
expect(screen.getByTestId('order-tif')).toHaveValue(
expectedOrder.timeInForce
);
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
expectedOrder.price
);
expect(screen.getByTestId('post-only')).toBeEnabled();
expect(screen.getByTestId('reduce-only')).toBeDisabled();
expect(screen.getByTestId('post-only')).toBeChecked();
expect(screen.getByTestId('reduce-only')).not.toBeChecked();
});
it('handles TIF select box dependent on order type', async () => {
@@ -167,6 +167,16 @@ export const DealTicket = ({
return disabled;
}, [order]);
const disableReduceOnlyCheckbox = useMemo(() => {
const disabled = order
? ![
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
].includes(order.timeInForce)
: true;
return disabled;
}, [order]);
const onSubmit = useCallback(
(order: OrderSubmission) => {
const now = new Date().getTime();
@@ -211,8 +221,18 @@ export const DealTicket = ({
if (type === OrderType.TYPE_NETWORK) return;
update({
type,
// when changing type also update the tif to what was last used of new type
// when changing type also update the TIF to what was last used of new type
timeInForce: lastTIF[type] || order.timeInForce,
postOnly:
type === OrderType.TYPE_MARKET ? false : order.postOnly,
reduceOnly:
type === OrderType.TYPE_LIMIT &&
![
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(lastTIF[type] || order.timeInForce)
? false
: order.postOnly,
expiresAt: undefined,
});
clearErrors('expiresAt');
@@ -260,8 +280,23 @@ export const DealTicket = ({
value={order.timeInForce}
orderType={order.type}
onSelect={(timeInForce) => {
update({ timeInForce, postOnly: false, reduceOnly: false });
// Set tif value for the given order type, so that when switching
// Reset post only and reduce only when changing TIF
update({
timeInForce,
postOnly: [
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(timeInForce)
? false
: order.postOnly,
reduceOnly: ![
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(timeInForce)
? false
: order.reduceOnly,
});
// Set TIF value for the given order type, so that when switching
// types we know the last used TIF for the given order type
setLastTIF((curr) => ({
...curr,
@@ -336,6 +371,7 @@ export const DealTicket = ({
<Checkbox
name="reduce-only"
checked={order.reduceOnly}
disabled={disableReduceOnlyCheckbox}
onCheckedChange={() => {
update({ postOnly: false, reduceOnly: !order.reduceOnly });
}}
@@ -343,9 +379,13 @@ export const DealTicket = ({
<Tooltip
description={
<span>
{t(
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.'
)}
{disableReduceOnlyCheckbox
? t(
'"Reduce only" can be used only with non-persistent orders, such as "Fill or Kill" or "Immediate or Cancel".'
)
: t(
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.'
)}
</span>
}
>
@@ -140,8 +140,6 @@ describe('Network switcher', () => {
[Networks.STAGNET3]: 'https://stag3.net',
[Networks.DEVNET]: 'https://dev.net',
[Networks.STAGNET1]: 'https://stag1.net',
[Networks.SANDBOX]: 'https://sandbox.net',
[Networks.MIRROR]: 'https://mirror.net',
};
// @ts-ignore Typescript doesn't know about this module being mocked
useEnvironment.mockImplementation(() => ({
@@ -181,8 +179,6 @@ describe('Network switcher', () => {
[Networks.STAGNET3]: 'https://stag3.net',
[Networks.DEVNET]: 'https://dev.net',
[Networks.STAGNET1]: 'https://stag1.net',
[Networks.SANDBOX]: 'https://sandbox.net',
[Networks.MIRROR]: 'https://mirror.net',
};
// @ts-ignore Typescript doesn't know about this module being mocked
useEnvironment.mockImplementation(() => ({
@@ -215,8 +211,6 @@ describe('Network switcher', () => {
[Networks.STAGNET3]: 'https://stag3.net',
[Networks.DEVNET]: 'https://dev.net',
[Networks.STAGNET1]: 'https://stag1.net',
[Networks.SANDBOX]: 'https://sandbox.net',
[Networks.MIRROR]: 'https://mirror.net',
};
// @ts-ignore Typescript doesn't know about this module being mocked
useEnvironment.mockImplementation(() => ({
@@ -17,12 +17,10 @@ export const envNameMapping: Record<Networks, string> = {
[Networks.VALIDATOR_TESTNET]: t('VALIDATOR_TESTNET'),
[Networks.CUSTOM]: t('Custom'),
[Networks.DEVNET]: t('Devnet'),
[Networks.SANDBOX]: t('Sandbox'),
[Networks.STAGNET1]: t('Stagnet'),
[Networks.STAGNET3]: t('Stagnet3'),
[Networks.TESTNET]: t('Fairground testnet'),
[Networks.MAINNET]: t('Mainnet'),
[Networks.MIRROR]: t('Mainnet mirror'),
};
export const envTriggerMapping: Record<Networks, string> = {
@@ -32,7 +30,6 @@ export const envTriggerMapping: Record<Networks, string> = {
export const envDescriptionMapping: Record<Networks, string> = {
[Networks.CUSTOM]: '',
[Networks.SANDBOX]: t('A playground test environment'),
[Networks.VALIDATOR_TESTNET]: t('The validator deployed testnet'),
[Networks.DEVNET]: t('The latest Vega code auto-deployed'),
[Networks.STAGNET1]: t('A release candidate for the staging environment'),
@@ -41,9 +38,6 @@ export const envDescriptionMapping: Record<Networks, string> = {
'Public testnet run by the Vega team, often used for incentives'
),
[Networks.MAINNET]: t('The vega mainnet'),
[Networks.MIRROR]: t(
'A mirror of the mainnet environment running on an Ethereum test network'
),
};
const standardNetworkKeys = [Networks.MAINNET, Networks.TESTNET];
+2 -3
View File
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
import { Networks } from '../types';
import { useEnvironment } from './use-environment';
type Net = Exclude<Networks, 'CUSTOM' | 'SANDBOX'>;
type Net = Exclude<Networks, 'CUSTOM'>;
export enum DApp {
Explorer = 'Explorer',
Console = 'Console',
@@ -21,7 +21,6 @@ const EmptyLinks: DAppLinks = {
[Networks.STAGNET3]: '',
[Networks.TESTNET]: '',
[Networks.MAINNET]: '',
[Networks.MIRROR]: '',
};
const ExplorerLinks = {
@@ -61,7 +60,7 @@ export const useLinks = (dapp: DApp, network?: Net) => {
};
let net = network || VEGA_ENV;
if (net === Networks.CUSTOM || net === Networks.SANDBOX) {
if (net === Networks.CUSTOM) {
net = Networks.TESTNET;
}
-2
View File
@@ -5,13 +5,11 @@ import type { envSchema } from './utils/validate-environment';
export enum Networks {
VALIDATOR_TESTNET = 'VALIDATOR_TESTNET',
CUSTOM = 'CUSTOM',
SANDBOX = 'SANDBOX',
TESTNET = 'TESTNET',
STAGNET1 = 'STAGNET1',
STAGNET3 = 'STAGNET3',
DEVNET = 'DEVNET',
MAINNET = 'MAINNET',
MIRROR = 'MIRROR',
}
export type Environment = z.infer<typeof envSchema>;
export type Configuration = z.infer<typeof tomlConfigSchema>;
@@ -3,13 +3,11 @@ import z from 'zod';
export enum Networks {
VALIDATOR_TESTNET = 'VALIDATOR_TESTNET',
CUSTOM = 'CUSTOM',
SANDBOX = 'SANDBOX',
TESTNET = 'TESTNET',
STAGNET1 = 'STAGNET1',
STAGNET3 = 'STAGNET3',
DEVNET = 'DEVNET',
MAINNET = 'MAINNET',
MIRROR = 'MIRROR',
}
const schemaObject = {
@@ -103,13 +103,3 @@ query LiquidityProviderFeeShare($marketId: ID!) {
}
}
}
subscription LiquidityProviderFeeShareUpdate($marketId: ID!) {
marketsData(marketIds: [$marketId]) {
liquidityProviderFeeShare {
partyId
equityLikeShare
averageEntryValuation
}
}
}
+1 -42
View File
@@ -36,13 +36,6 @@ export type LiquidityProviderFeeShareQueryVariables = Types.Exact<{
export type LiquidityProviderFeeShareQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, data?: { __typename?: 'MarketData', market: { __typename?: 'Market', id: string }, liquidityProviderFeeShare?: Array<{ __typename?: 'LiquidityProviderFeeShare', equityLikeShare: string, averageEntryValuation: string, party: { __typename?: 'Party', id: string } }> | null } | null } | null };
export type LiquidityProviderFeeShareUpdateSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type LiquidityProviderFeeShareUpdateSubscription = { __typename?: 'Subscription', marketsData: Array<{ __typename?: 'ObservableMarketData', liquidityProviderFeeShare?: Array<{ __typename?: 'ObservableLiquidityProviderFeeShare', partyId: string, equityLikeShare: string, averageEntryValuation: string }> | null }> };
export const LiquidityProvisionFieldsFragmentDoc = gql`
fragment LiquidityProvisionFields on LiquidityProvision {
party {
@@ -256,38 +249,4 @@ export function useLiquidityProviderFeeShareLazyQuery(baseOptions?: Apollo.LazyQ
}
export type LiquidityProviderFeeShareQueryHookResult = ReturnType<typeof useLiquidityProviderFeeShareQuery>;
export type LiquidityProviderFeeShareLazyQueryHookResult = ReturnType<typeof useLiquidityProviderFeeShareLazyQuery>;
export type LiquidityProviderFeeShareQueryResult = Apollo.QueryResult<LiquidityProviderFeeShareQuery, LiquidityProviderFeeShareQueryVariables>;
export const LiquidityProviderFeeShareUpdateDocument = gql`
subscription LiquidityProviderFeeShareUpdate($marketId: ID!) {
marketsData(marketIds: [$marketId]) {
liquidityProviderFeeShare {
partyId
equityLikeShare
averageEntryValuation
}
}
}
`;
/**
* __useLiquidityProviderFeeShareUpdateSubscription__
*
* To run a query within a React component, call `useLiquidityProviderFeeShareUpdateSubscription` and pass it any options that fit your needs.
* When your component renders, `useLiquidityProviderFeeShareUpdateSubscription` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useLiquidityProviderFeeShareUpdateSubscription({
* variables: {
* marketId: // value for 'marketId'
* },
* });
*/
export function useLiquidityProviderFeeShareUpdateSubscription(baseOptions: Apollo.SubscriptionHookOptions<LiquidityProviderFeeShareUpdateSubscription, LiquidityProviderFeeShareUpdateSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<LiquidityProviderFeeShareUpdateSubscription, LiquidityProviderFeeShareUpdateSubscriptionVariables>(LiquidityProviderFeeShareUpdateDocument, options);
}
export type LiquidityProviderFeeShareUpdateSubscriptionHookResult = ReturnType<typeof useLiquidityProviderFeeShareUpdateSubscription>;
export type LiquidityProviderFeeShareUpdateSubscriptionResult = Apollo.SubscriptionResult<LiquidityProviderFeeShareUpdateSubscription>;
export type LiquidityProviderFeeShareQueryResult = Apollo.QueryResult<LiquidityProviderFeeShareQuery, LiquidityProviderFeeShareQueryVariables>;
@@ -6,7 +6,6 @@ import produce from 'immer';
import {
LiquidityProviderFeeShareDocument,
LiquidityProviderFeeShareUpdateDocument,
LiquidityProvisionsDocument,
LiquidityProvisionsUpdateDocument,
MarketLpDocument,
@@ -18,7 +17,6 @@ import type {
LiquidityProviderFeeShareFieldsFragment,
LiquidityProviderFeeShareQuery,
LiquidityProviderFeeShareQueryVariables,
LiquidityProviderFeeShareUpdateSubscription,
LiquidityProvisionFieldsFragment,
LiquidityProvisionsQuery,
LiquidityProvisionsQueryVariables,
@@ -115,42 +113,14 @@ export const marketLiquidityDataProvider = makeDataProvider<
export const liquidityFeeShareDataProvider = makeDataProvider<
LiquidityProviderFeeShareQuery,
LiquidityProviderFeeShareFieldsFragment[],
LiquidityProviderFeeShareUpdateSubscription,
LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare'],
never,
never,
LiquidityProviderFeeShareQueryVariables
>({
query: LiquidityProviderFeeShareDocument,
subscriptionQuery: LiquidityProviderFeeShareUpdateDocument,
update: (
data: LiquidityProviderFeeShareFieldsFragment[] | null,
deltas: LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare']
) => {
return produce(data || [], (draft) => {
deltas?.forEach((delta) => {
const id = delta.partyId;
const index = draft.findIndex((a) => a.party.id === id);
if (index !== -1) {
draft[index].equityLikeShare = delta.equityLikeShare;
draft[index].averageEntryValuation = delta.averageEntryValuation;
} else {
draft.unshift({
equityLikeShare: delta.equityLikeShare,
averageEntryValuation: delta.averageEntryValuation,
party: {
id: delta.partyId,
},
// TODO add accounts connection to the subscription
});
}
});
});
},
getData: (data) => {
return data?.market?.data?.liquidityProviderFeeShare || [];
},
getDelta: (subscriptionData: LiquidityProviderFeeShareUpdateSubscription) => {
return subscriptionData.marketsData[0].liquidityProviderFeeShare;
},
});
export type Filter = { partyId?: string; active?: boolean };
@@ -14,6 +14,8 @@ fragment OrderFields on Order {
expiresAt
createdAt
updatedAt
postOnly
reduceOnly
liquidityProvision {
__typename
}
@@ -30,12 +32,16 @@ query OrderById($orderId: ID!) {
query Orders(
$partyId: ID!
$marketIds: [ID!]
$pagination: Pagination
$filter: OrderByMarketIdsFilter
$filter: OrderFilter
) {
party(id: $partyId) {
id
ordersConnection(pagination: $pagination, filter: $filter) {
ordersConnection(
pagination: $pagination
filter: { order: $filter, marketIds: $marketIds }
) {
edges {
node {
...OrderFields
@@ -72,8 +78,8 @@ fragment OrderUpdateFields on OrderUpdate {
}
}
subscription OrdersUpdate($partyId: ID!) {
orders(filter: { partyIds: [$partyId] }) {
subscription OrdersUpdate($partyId: ID!, $marketIds: [ID!]) {
orders(filter: { partyIds: [$partyId], marketIds: $marketIds }) {
...OrderUpdateFields
}
}
@@ -3,28 +3,30 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type OrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null };
export type OrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null };
export type OrderByIdQueryVariables = Types.Exact<{
orderId: Types.Scalars['ID'];
}>;
export type OrderByIdQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } };
export type OrderByIdQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } };
export type OrdersQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
marketIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
pagination?: Types.InputMaybe<Types.Pagination>;
filter?: Types.InputMaybe<Types.OrderByMarketIdsFilter>;
filter?: Types.InputMaybe<Types.OrderFilter>;
}>;
export type OrdersQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, ordersConnection?: { __typename?: 'OrderConnection', edges?: Array<{ __typename?: 'OrderEdge', cursor?: string | null, node: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } }> | null, pageInfo?: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } | null } | null } | null };
export type OrdersQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, ordersConnection?: { __typename?: 'OrderConnection', edges?: Array<{ __typename?: 'OrderEdge', cursor?: string | null, node: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } }> | null, pageInfo?: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } | null } | null } | null };
export type OrderUpdateFieldsFragment = { __typename?: 'OrderUpdate', id: string, marketId: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, liquidityProvisionId?: string | null, peggedOrder?: { __typename: 'PeggedOrder' } | null };
export type OrdersUpdateSubscriptionVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
marketIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
}>;
@@ -47,6 +49,8 @@ export const OrderFieldsFragmentDoc = gql`
expiresAt
createdAt
updatedAt
postOnly
reduceOnly
liquidityProvision {
__typename
}
@@ -112,10 +116,13 @@ export type OrderByIdQueryHookResult = ReturnType<typeof useOrderByIdQuery>;
export type OrderByIdLazyQueryHookResult = ReturnType<typeof useOrderByIdLazyQuery>;
export type OrderByIdQueryResult = Apollo.QueryResult<OrderByIdQuery, OrderByIdQueryVariables>;
export const OrdersDocument = gql`
query Orders($partyId: ID!, $pagination: Pagination, $filter: OrderByMarketIdsFilter) {
query Orders($partyId: ID!, $marketIds: [ID!], $pagination: Pagination, $filter: OrderFilter) {
party(id: $partyId) {
id
ordersConnection(pagination: $pagination, filter: $filter) {
ordersConnection(
pagination: $pagination
filter: {order: $filter, marketIds: $marketIds}
) {
edges {
node {
...OrderFields
@@ -146,6 +153,7 @@ export const OrdersDocument = gql`
* const { data, loading, error } = useOrdersQuery({
* variables: {
* partyId: // value for 'partyId'
* marketIds: // value for 'marketIds'
* pagination: // value for 'pagination'
* filter: // value for 'filter'
* },
@@ -163,8 +171,8 @@ export type OrdersQueryHookResult = ReturnType<typeof useOrdersQuery>;
export type OrdersLazyQueryHookResult = ReturnType<typeof useOrdersLazyQuery>;
export type OrdersQueryResult = Apollo.QueryResult<OrdersQuery, OrdersQueryVariables>;
export const OrdersUpdateDocument = gql`
subscription OrdersUpdate($partyId: ID!) {
orders(filter: {partyIds: [$partyId]}) {
subscription OrdersUpdate($partyId: ID!, $marketIds: [ID!]) {
orders(filter: {partyIds: [$partyId], marketIds: $marketIds}) {
...OrderUpdateFields
}
}
@@ -183,6 +191,7 @@ export const OrdersUpdateDocument = gql`
* const { data, loading, error } = useOrdersUpdateSubscription({
* variables: {
* partyId: // value for 'partyId'
* marketIds: // value for 'marketIds'
* },
* });
*/
@@ -112,9 +112,7 @@ describe('order data provider', () => {
const updatedData = update(data, delta, () => null, {
partyId: '0x123',
filter: {
order: {
dateRange: { end: new Date('2022-02-01').toISOString() },
},
dateRange: { end: new Date('2022-02-01').toISOString() },
},
});
expect(
@@ -35,45 +35,40 @@ const orderMatchFilters = (
return true;
}
if (
variables?.filter?.order?.status &&
!(order.status && variables.filter.order.status.includes(order.status))
variables?.filter?.status &&
!(order.status && variables.filter.status.includes(order.status))
) {
return false;
}
if (
variables?.filter?.order?.types &&
!(order.type && variables.filter.order.types.includes(order.type))
variables?.filter?.types &&
!(order.type && variables.filter.types.includes(order.type))
) {
return false;
}
if (
variables?.filter?.order?.timeInForce &&
!variables.filter.order.timeInForce.includes(order.timeInForce)
variables?.filter?.timeInForce &&
!variables.filter.timeInForce.includes(order.timeInForce)
) {
return false;
}
if (
variables?.filter?.order?.excludeLiquidity &&
order.liquidityProvisionId
) {
if (variables?.filter?.excludeLiquidity && order.liquidityProvisionId) {
return false;
}
if (
variables?.filter?.order?.dateRange?.start &&
variables?.filter?.dateRange?.start &&
!(
(order.updatedAt || order.createdAt) &&
variables.filter.order.dateRange.start <
(order.updatedAt || order.createdAt)
variables.filter.dateRange.start < (order.updatedAt || order.createdAt)
)
) {
return false;
}
if (
variables?.filter?.order?.dateRange?.end &&
variables?.filter?.dateRange?.end &&
!(
(order.updatedAt || order.createdAt) &&
variables.filter.order.dateRange.end >
(order.updatedAt || order.createdAt)
variables.filter.dateRange.end > (order.updatedAt || order.createdAt)
)
) {
return false;
@@ -243,18 +238,17 @@ export const hasActiveOrderProvider = makeDerivedDataProvider<
{ partyId: string; marketId?: string }
>(
[
(callback, client, variables) =>
(callback, client, { partyId, marketId }) =>
hasActiveOrderProviderInternal(callback, client, {
marketIds: marketId ? [marketId] : undefined,
filter: {
order: {
status: [OrderStatus.STATUS_ACTIVE],
excludeLiquidity: true,
},
status: [OrderStatus.STATUS_ACTIVE],
excludeLiquidity: true,
},
pagination: {
first: 1,
},
...variables,
partyId,
} as OrdersQueryVariables),
],
(parts) => parts[0]
@@ -72,12 +72,10 @@ export const useOrderListData = ({
const allVars: OrdersQueryVariables & OrdersUpdateSubscriptionVariables = {
partyId,
filter: {
order: {
dateRange: filter?.updatedAt?.value,
status: filter?.status?.value,
timeInForce: filter?.timeInForce?.value,
types: filter?.type?.value,
},
dateRange: filter?.updatedAt?.value,
status: filter?.status?.value,
timeInForce: filter?.timeInForce?.value,
types: filter?.type?.value,
},
pagination: {
first: 1000,
@@ -129,8 +129,9 @@ export const OrderListTable = memo(
}: VegaValueFormatterParams<Order, 'status'>) => {
if (data?.rejectionReason && value) {
return `${Schema.OrderStatusMapping[value]}: ${
data?.rejectionReason &&
Schema.OrderRejectionReasonMapping[data.rejectionReason]
(data?.rejectionReason &&
Schema.OrderRejectionReasonMapping[data.rejectionReason]) ||
data?.rejectionReason
}`;
}
return value ? Schema.OrderStatusMapping[value] : '';
@@ -218,7 +219,14 @@ export const OrderListTable = memo(
return `${Schema.OrderTimeInForceMapping[value]}: ${expiry}`;
}
return value ? Schema.OrderTimeInForceMapping[value] : '';
const tifLabel = value
? Schema.OrderTimeInForceMapping[value]
: '';
const label = `${tifLabel}${
data?.postOnly ? t('. Post Only') : ''
}${data?.reduceOnly ? t('. Reduce only') : ''}`;
return label;
}}
minWidth={150}
/>
@@ -53,14 +53,12 @@ export const useActiveOrdersVolumeAndMargin = (
update,
variables: {
partyId: partyId || '',
marketIds: [marketId],
filter: {
marketIds: [marketId],
order: {
status: [
OrderStatus.STATUS_ACTIVE,
OrderStatus.STATUS_PARTIALLY_FILLED,
],
},
status: [
OrderStatus.STATUS_ACTIVE,
OrderStatus.STATUS_PARTIALLY_FILLED,
],
},
},
skip: !partyId,
-1
View File
@@ -4,7 +4,6 @@ export * from './lib/positions-data-providers';
export * from './lib/margin-data-provider';
export * from './lib/margin-calculator';
export * from './lib/positions-table';
export * from './lib/use-close-position';
export * from './lib/use-market-margin';
export * from './lib/use-market-position-open-volume';
export * from './lib/use-open-volume';
@@ -1,109 +0,0 @@
import { useEnvironment } from '@vegaprotocol/environment';
import type { OrderFieldsFragment } from '@vegaprotocol/orders';
import { truncateByChars } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { Link } from '@vegaprotocol/ui-toolkit';
import type { TransactionResult, VegaTxState } from '@vegaprotocol/wallet';
import type { ClosingOrder as IClosingOrder } from '../use-close-position';
import { useRequestClosePositionData } from '../use-request-close-position-data';
import { ClosingOrder } from './shared';
interface CompleteProps {
partyId: string;
transaction: VegaTxState;
transactionResult?: TransactionResult;
closingOrder?: IClosingOrder;
closingOrderResult?: OrderFieldsFragment;
}
export const Complete = ({
partyId,
transaction,
transactionResult,
closingOrder,
closingOrderResult,
}: CompleteProps) => {
const { VEGA_EXPLORER_URL } = useEnvironment();
if (!transactionResult || !closingOrderResult) return null;
return (
<>
{closingOrderResult.status === Schema.OrderStatus.STATUS_FILLED &&
transactionResult.status ? (
<Success partyId={partyId} order={closingOrder} />
) : (
<Error
transactionResult={transactionResult}
closingOrderResult={closingOrderResult}
/>
)}
{transaction.txHash && (
<>
<p className="font-semibold mt-4">{t('Transaction')}</p>
<p>
<Link
href={`${VEGA_EXPLORER_URL}/txs/${transaction.txHash}`}
target="_blank"
>
{truncateByChars(transaction.txHash)}
</Link>
</p>
</>
)}
</>
);
};
const Success = ({
partyId,
order,
}: {
partyId: string;
order?: IClosingOrder;
}) => {
const { market, marketData, orders } = useRequestClosePositionData(
order?.marketId,
partyId
);
if (!market || !marketData || !orders) {
return <div>{t('Loading...')}</div>;
}
if (!order) {
return (
<div className="text-vega-pink">{t('Could retrieve closing order')}</div>
);
}
return (
<>
<h2 className="font-bold">{t('Position closed')}</h2>
<ClosingOrder order={order} market={market} marketData={marketData} />
</>
);
};
const Error = ({
transactionResult,
closingOrderResult,
}: {
transactionResult: TransactionResult;
closingOrderResult: OrderFieldsFragment;
}) => {
const reason =
closingOrderResult.rejectionReason &&
Schema.OrderRejectionReasonMapping[closingOrderResult.rejectionReason];
return (
<div className="text-vega-pink">
{reason ? (
<p>{reason}</p>
) : (
<p>
{t('Transaction failed')}: {transactionResult.error}
</p>
)}
</div>
);
};
@@ -1,102 +0,0 @@
import { render, screen, within } from '@testing-library/react';
import * as Schema from '@vegaprotocol/types';
import * as dataHook from '../use-request-close-position-data';
import { Requested } from './requested';
jest.mock('./use-request-close-position-data');
describe('Close position dialog - Request', () => {
const props = {
partyId: 'party-id',
order: {
marketId: 'market-id',
type: Schema.OrderType.TYPE_MARKET as const,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK as const,
side: Schema.Side.SIDE_BUY,
size: '10',
},
};
it('loading state', async () => {
jest.spyOn(dataHook, 'useRequestClosePositionData').mockReturnValue({
loading: false,
market: null,
marketData: null,
orders: [],
});
render(<Requested {...props} />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('renders message if no closing order found', async () => {
const orders = [
{
size: '200',
price: '999',
side: Schema.Side.SIDE_BUY,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
},
{
size: '300',
price: '888',
side: Schema.Side.SIDE_SELL,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
},
];
jest.spyOn(dataHook, 'useRequestClosePositionData').mockReturnValue({
market: {
decimalPlaces: 2,
positionDecimalPlaces: 2,
tradableInstrument: {
instrument: {
name: 'test market',
product: {
// @ts-ignore avoiding having to add every property on the type
settlementAsset: {
symbol: 'SYM',
},
},
},
},
},
// @ts-ignore avoid all fields
marketData: {
markPrice: '100',
},
// @ts-ignore avoid all fields
orders,
});
render(<Requested {...props} />);
// closing order
const closingOrderHeader = screen.getByText('Position to be closed');
const closingOrderTable = within(
closingOrderHeader.nextElementSibling?.querySelector(
'tbody'
) as HTMLElement
);
const closingOrderRow = closingOrderTable.getAllByRole('row');
expect(closingOrderRow[0].children[0]).toHaveTextContent('test market');
expect(closingOrderRow[0].children[1]).toHaveTextContent('+0.1');
expect(closingOrderRow[0].children[2]).toHaveTextContent('~1.00 SYM');
// orders
const ordersHeading = screen.getByText('Orders to be closed');
const ordersTable = within(
ordersHeading.nextElementSibling?.querySelector('tbody') as HTMLElement
);
const orderRows = ordersTable.getAllByRole('row');
expect(orderRows).toHaveLength(orders.length);
expect(orderRows[0].children[0]).toHaveTextContent('+2');
expect(orderRows[0].children[1]).toHaveTextContent('9.99 SYM');
expect(orderRows[0].children[2]).toHaveTextContent(
"Good 'til Cancelled (GTC)"
);
expect(orderRows[1].children[0]).toHaveTextContent('-3');
expect(orderRows[1].children[1]).toHaveTextContent('8.88 SYM');
expect(orderRows[1].children[2]).toHaveTextContent(
"Good 'til Cancelled (GTC)"
);
});
});
@@ -1,37 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { ClosingOrder as IClosingOrder } from '../use-close-position';
import { useRequestClosePositionData } from '../use-request-close-position-data';
import { ActiveOrders, ClosingOrder } from './shared';
export const Requested = ({
order,
partyId,
}: {
order?: IClosingOrder;
partyId: string;
}) => {
const { market, marketData, orders, loading } = useRequestClosePositionData(
order?.marketId,
partyId
);
if (loading || !market || !marketData || !orders) {
return <div>{t('Loading...')}</div>;
}
if (!order) {
return (
<div className="text-vega-pink">
{t('Could not create closing order')}
</div>
);
}
return (
<>
<h2 className="font-bold">{t('Position to be closed')}</h2>
<ClosingOrder order={order} market={market} marketData={marketData} />
<ActiveOrders market={market} orders={orders} />
</>
);
};
@@ -1,114 +0,0 @@
import type { MarketData, Market } from '@vegaprotocol/market-list';
import type { Order } from '@vegaprotocol/orders';
import { timeInForceLabel } from '@vegaprotocol/orders';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Size } from '@vegaprotocol/react-helpers';
import type { ReactNode } from 'react';
import type { ClosingOrder as IClosingOrder } from '../use-close-position';
export const ClosingOrder = ({
order,
market,
marketData,
}: {
order: IClosingOrder;
market: Market;
marketData: MarketData;
}) => {
const asset = market.tradableInstrument.instrument.product.settlementAsset;
const estimatedPrice =
marketData && market
? addDecimalsFormatNumber(marketData.markPrice, market.decimalPlaces)
: '-';
const size = market ? (
<Size
value={order.size}
side={order.side}
positionDecimalPlaces={market.positionDecimalPlaces}
/>
) : (
'-'
);
return (
<BasicTable
headers={[t('Market'), t('Amount'), t('Est price')]}
rows={[
[
market.tradableInstrument.instrument.name,
size,
`~${estimatedPrice} ${asset?.symbol}`,
],
]}
/>
);
};
export const ActiveOrders = ({
market,
orders,
}: {
market: Market;
orders: Order[];
}) => {
const asset = market.tradableInstrument.instrument.product.settlementAsset;
if (!orders.length) {
return null;
}
return (
<div className="mt-4">
<h2 className="font-bold">{t('Orders to be closed')}</h2>
<BasicTable
headers={[t('Amount'), t('Target price'), t('Time in force')]}
rows={orders.map((o) => {
return [
<Size
value={o.size}
side={o.side}
positionDecimalPlaces={market.positionDecimalPlaces}
/>,
`${addDecimalsFormatNumber(o.price, market.decimalPlaces)} ${
asset.symbol
}`,
timeInForceLabel(o.timeInForce),
];
})}
/>
</div>
);
};
interface BasicTableProps {
headers: ReactNode[];
rows: ReactNode[][];
}
const BasicTable = ({ headers, rows }: BasicTableProps) => {
return (
<table className="w-full">
<thead>
<tr>
{headers.map((h, i) => (
<th key={i} className="text-left font-medium">
{h}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((cells, i) => (
<tr key={i}>
{cells.map((c, i) => (
<td key={i} className="align-top">
{c}
</td>
))}
</tr>
))}
</tbody>
</table>
);
};
@@ -349,16 +349,15 @@ export const volumeAndMarginProvider = makeDerivedDataProvider<
PositionsQueryVariables & MarketDataQueryVariables
>(
[
(callback, client, variables) =>
(callback, client, { partyId, marketId }) =>
ordersProvider(callback, client, {
...variables,
partyId,
marketIds: [marketId],
filter: {
order: {
status: [
OrderStatus.STATUS_ACTIVE,
OrderStatus.STATUS_PARTIALLY_FILLED,
],
},
status: [
OrderStatus.STATUS_ACTIVE,
OrderStatus.STATUS_PARTIALLY_FILLED,
],
},
}),
(callback, client, variables) =>
@@ -1,211 +0,0 @@
import type { ReactNode } from 'react';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { renderHook, waitFor } from '@testing-library/react';
import * as Types from '@vegaprotocol/types';
import { useClosePosition } from './use-close-position';
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { initialState } from '@vegaprotocol/wallet';
import type { TransactionEventSubscription } from '@vegaprotocol/wallet';
import { TransactionEventDocument } from '@vegaprotocol/wallet';
import { act } from 'react-dom/test-utils';
import type { OrderSubSubscription } from '@vegaprotocol/orders';
import { OrderSubDocument } from '@vegaprotocol/orders';
const pubKey = 'test-pubkey';
const defaultWalletContext = {
pubKey,
pubKeys: [{ publicKey: pubKey, name: 'test pubkey' }],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),
selectPubKey: jest.fn(),
connector: null,
};
const txResult = {
__typename: 'TransactionResult',
partyId: pubKey,
hash: '0x123',
status: true,
error: null,
};
function setup(context?: Partial<VegaWalletContextShape>) {
const mockTransactionResult: MockedResponse<TransactionEventSubscription> = {
request: {
query: TransactionEventDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
busEvents: [
{
type: Types.BusEventType.TransactionResult,
event: txResult,
__typename: 'BusEvent',
},
] as TransactionEventSubscription['busEvents'],
},
},
};
const mockOrderResult: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
orders: [
{
type: Types.OrderType.TYPE_LIMIT,
id: '2fca514cebf9f465ae31ecb4c5721e3a6f5f260425ded887ca50ba15b81a5d50',
status: Types.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Types.Side.SIDE_BUY,
marketId: 'market-id',
__typename: 'OrderUpdate',
},
],
},
},
};
const wrapper = ({ children }: { children: ReactNode }) => (
<MockedProvider mocks={[mockTransactionResult, mockOrderResult]}>
<VegaWalletContext.Provider
value={{ ...defaultWalletContext, ...context }}
>
{children}
</VegaWalletContext.Provider>
</MockedProvider>
);
return renderHook(() => useClosePosition(), { wrapper });
}
describe('useClosePosition', () => {
const txResponse = {
signature:
'cfe592d169f87d0671dd447751036d0dddc165b9c4b65e5a5060e2bbadd1aa726d4cbe9d3c3b327bcb0bff4f83999592619a2493f9bbd251fae99ce7ce766909',
transactionHash: '0x123',
};
it('doesnt send the tx if there is no open volume', () => {
const mockSend = jest.fn();
const { result } = setup({ sendTx: mockSend });
expect(result.current).toEqual({
submit: expect.any(Function),
transaction: initialState,
Dialog: expect.any(Function),
});
result.current.submit({ marketId: 'test-market', openVolume: '0' });
expect(mockSend).not.toBeCalled();
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
});
it('doesnt send the tx if there is no pubkey', () => {
const mockSend = jest.fn();
const { result } = setup({ sendTx: mockSend, pubKey: null });
result.current.submit({ marketId: 'test-market', openVolume: '1000' });
expect(mockSend).not.toBeCalled();
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
});
it('closes long positions', async () => {
const marketId = 'test-market';
const openVolume = '1000';
const mockSend = jest.fn().mockResolvedValue(txResponse);
const { result } = setup({ sendTx: mockSend, pubKey });
act(() => {
result.current.submit({ marketId, openVolume });
});
expect(mockSend).toBeCalledWith(defaultWalletContext.pubKey, {
batchMarketInstructions: {
cancellations: [
{
marketId,
orderId: '',
},
],
submissions: [
{
marketId,
type: Types.OrderType.TYPE_MARKET,
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_SELL,
size: openVolume,
},
],
},
});
expect(result.current.transaction.status).toEqual(VegaTxStatus.Requested);
await waitFor(() => {
expect(result.current.transaction).toEqual({
status: VegaTxStatus.Complete,
signature: txResponse.signature,
txHash: txResponse.transactionHash,
dialogOpen: true,
error: null,
});
expect(result.current.transactionResult).toEqual(txResult);
});
});
it('closes short positions', async () => {
const marketId = 'test-market';
const openVolume = '-1000';
const mockSend = jest.fn().mockResolvedValue(txResponse);
const { result } = setup({ sendTx: mockSend, pubKey });
act(() => {
result.current.submit({ marketId, openVolume });
});
expect(mockSend).toBeCalledWith(defaultWalletContext.pubKey, {
batchMarketInstructions: {
cancellations: [
{
marketId,
orderId: '',
},
],
submissions: [
{
marketId,
type: Types.OrderType.TYPE_MARKET,
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
size: openVolume.replace('-', ''),
},
],
},
});
expect(result.current.transaction.status).toEqual(VegaTxStatus.Requested);
await waitFor(() => {
expect(result.current.transaction).toEqual({
status: VegaTxStatus.Complete,
signature: txResponse.signature,
txHash: txResponse.transactionHash,
dialogOpen: true,
error: null,
});
expect(result.current.transactionResult).toEqual(txResult);
});
});
});
@@ -1,105 +0,0 @@
import { useCallback, useState } from 'react';
import type { TransactionResult } from '@vegaprotocol/wallet';
import { determineId } from '@vegaprotocol/wallet';
import { useVegaWallet, useTransactionResult } from '@vegaprotocol/wallet';
import { useVegaTransaction } from '@vegaprotocol/wallet';
import * as Sentry from '@sentry/react';
import * as Schema from '@vegaprotocol/types';
import { useOrderUpdate } from '@vegaprotocol/orders';
import type { OrderSubFieldsFragment } from '@vegaprotocol/orders';
export interface ClosingOrder {
marketId: string;
type: Schema.OrderType.TYPE_MARKET;
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK;
side: Schema.Side;
size: string;
}
export const useClosePosition = () => {
const { pubKey } = useVegaWallet();
const { send, transaction, setComplete, Dialog } = useVegaTransaction();
const [closingOrder, setClosingOrder] = useState<ClosingOrder>();
const [closingOrderResult, setClosingOrderResult] =
useState<OrderSubFieldsFragment>();
const [transactionResult, setTransactionResult] =
useState<TransactionResult>();
const waitForTransactionResult = useTransactionResult();
const waitForOrder = useOrderUpdate(transaction);
const submit = useCallback(
async ({
marketId,
openVolume,
}: {
marketId: string;
openVolume: string;
}) => {
if (!pubKey || openVolume === '0') {
return;
}
setTransactionResult(undefined);
setClosingOrder(undefined);
try {
// figure out if position is long or short and make side the opposite
const side = openVolume.startsWith('-')
? Schema.Side.SIDE_BUY
: Schema.Side.SIDE_SELL;
// volume could be prefixed with '-' if position is short, remove it
const size = openVolume.replace('-', '');
const closingOrder = {
marketId: marketId,
type: Schema.OrderType.TYPE_MARKET as const,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK as const,
side,
size,
};
setClosingOrder(closingOrder);
const command = {
batchMarketInstructions: {
cancellations: [
{
marketId,
orderId: '', // omit order id to cancel all active orders
},
],
submissions: [closingOrder],
},
};
const res = await send(pubKey, command);
if (res) {
const orderId = determineId(res.signature);
const [txResult, orderResult] = await Promise.all([
waitForTransactionResult(res.transactionHash, pubKey),
waitForOrder(orderId, pubKey),
]);
setTransactionResult(txResult);
setClosingOrderResult(orderResult);
setComplete();
}
return res;
} catch (e) {
Sentry.captureException(e);
return;
}
},
[pubKey, send, setComplete, waitForTransactionResult, waitForOrder]
);
return {
transaction,
transactionResult,
submit,
closingOrder,
closingOrderResult,
Dialog,
};
};
@@ -1,66 +0,0 @@
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { isOrderActive, ordersWithMarketProvider } from '@vegaprotocol/orders';
import type { OrdersQueryVariables } from '@vegaprotocol/orders';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react';
export const useRequestClosePositionData = (
marketId?: string,
partyId?: string
) => {
const marketVariables = useMemo(
() => ({ marketId: marketId || '' }),
[marketId]
);
const orderVariables = useMemo<OrdersQueryVariables>(
() => ({ partyId: partyId || '' }),
[partyId]
);
const { data: market, loading: marketLoading } = useDataProvider({
dataProvider: marketProvider,
variables: marketVariables,
skip: !marketId,
});
const { data: marketData, loading: marketDataLoading } = useDataProvider({
dataProvider: marketDataProvider,
variables: marketVariables,
});
const { data: orderData, loading: orderDataLoading } = useDataProvider({
dataProvider: ordersWithMarketProvider,
variables: orderVariables,
skip: !partyId,
});
const orders = useMemo(() => {
if (!orderData || !market) return [];
return (
orderData
.filter((o) => {
// Filter out orders not on market for position
if (
!o ||
!o.node ||
!o.node.market ||
o.node.market.id !== market.id
) {
return false;
}
if (!isOrderActive(o.node.status)) {
return false;
}
return true;
})
// @ts-ignore o is never null as its been filtered out above
.map((o) => o.node)
);
}, [orderData, market]);
return {
market,
marketData,
orders,
loading: marketLoading || marketDataLoading || orderDataLoading,
};
};
@@ -38,6 +38,7 @@ export const Checkbox = ({
checked={checked}
onCheckedChange={onCheckedChange}
disabled={disabled}
data-testid={name}
>
<CheckboxPrimitive.CheckboxIndicator className="flex justify-center items-center w-[15px] h-[15px] bg-black dark:bg-white">
{checked === 'indeterminate' ? (