Compare commits

...
Author SHA1 Message Date
asiaznik 88992fa6e6 chore: rebased 2023-02-06 20:51:10 +01:00
asiaznik abe650cf95 chore: order rejection case after rebase 2023-02-06 20:48:05 +01:00
asiaznik 2298c434c8 chore: addressed PR comments 2023-02-06 20:48:05 +01:00
asiaznik 41126111a5 fix: dismiss all btn 2023-02-06 20:48:05 +01:00
asiaznik f4b11e83df fix: lint 2023-02-06 20:48:05 +01:00
asiaznik 0e77062cc6 fix: auto fix 2023-02-06 20:48:05 +01:00
asiaznik 1391db5eed chore: new designs 2023-02-06 20:48:05 +01:00
asiaznik 717b470a9e chore: auto close after 5s for final stages of tx 2023-02-06 20:48:05 +01:00
asiaznik 4df59b9c64 fix: lint housekeeping 2023-02-06 20:48:05 +01:00
asiaznik 23bc8335cc chore: refactored toasts 2023-02-06 20:47:01 +01:00
Matthew Russell 8bcdaf4cda feat(trading): key to key transfers (#2784) 2023-02-06 11:35:40 -08:00
mattrussell36 3eb1359504 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 18:08:25 +00:00
Sam Keen 39cf8ad4d6 feat(governance): remove some performance info from validator mouseover (#2865) 2023-02-06 17:08:34 +00:00
Edd faef98f0ae fix(explorer): type generation broken in an oracle refactor (#2859) 2023-02-06 13:29:23 +00:00
macqbat 710e2daa27 chore(trading): notification component redesign (#2845) 2023-02-06 13:31:35 +01:00
daro-maj 06ea3924fa chore(trading): increase withdraw ac test coverage (#2853) 2023-02-06 13:27:12 +01:00
mattrussell36 965e2d8972 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 12:09:01 +00:00
m.ray 73de2fed43 fix(trading): when order is rejected change tx toast intent (#2851) 2023-02-06 11:44:04 +00:00
Sam Keen 536859e067 feat(governance): visual tweaks to main nav (#2839) 2023-02-06 11:18:14 +00:00
Sam Keen 54bfaef473 fix(governance): empty div disrupts other page elements (#2855) 2023-02-06 10:35:26 +00:00
Matthew Russell e2cad707a7 chore(trading): add validator testnet env file (#2852) 2023-02-06 09:04:34 +00:00
mattrussell36 ec8d9798ab chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 06:08:16 +00:00
mattrussell36 e629c1e81a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 00:10:27 +00:00
mattrussell36 25feed793c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 18:08:10 +00:00
mattrussell36 2867367e16 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 12:07:47 +00:00
mattrussell36 50b408e74d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 06:05:57 +00:00
mattrussell36 8f531c7c05 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 00:13:19 +00:00
mattrussell36 350d16fee1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 18:07:27 +00:00
mattrussell36 c052c66266 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 12:06:25 +00:00
mattrussell36 e2032df236 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 06:07:41 +00:00
Matthew Russell 9ece19869d chore(trading): update order subs to not use busevents (#2849) 2023-02-03 16:12:20 -08:00
Joe Tsang 22abc8160c test(token): pending balances (#2843) 2023-02-03 19:26:17 +00:00
Edd b1a7a22bf9 feat(explorer): add protocol upgrade view (#2802) 2023-02-03 19:25:36 +00:00
mattrussell36 c4b38cc0dc chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 18:05:46 +00:00
m.ray 44d6d931d5 fix(trading): update async renderer on error (#2842) 2023-02-03 16:52:10 +00:00
m.ray 7f1e47d7fd fix(trading): remove price flash cell from PNL (#2829) 2023-02-03 14:59:24 +00:00
Art 0eddfa4073 fix(ui-toolkit): dark and light shades mismatch (#2841) 2023-02-03 12:33:13 +00:00
mattrussell36 83a034ce91 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 12:06:17 +00:00
5ab5aa01a2 feat(trading): 2803 console announcement banner (#2830)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2023-02-03 10:34:32 +00:00
mattrussell36 b22f9156b5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 06:07:49 +00:00
mattrussell36 bab5e2018b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 00:12:30 +00:00
macqbat 3f7692ce75 chore(trading): add alpha font to trading (#2833) 2023-02-02 19:25:14 +01:00
mattrussell36 b69fb3b0e5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 18:08:33 +00:00
macqbat 83af6e74f6 chore(trading): no data message in charts (#2816) 2023-02-02 18:43:16 +01:00
131 changed files with 3255 additions and 1629 deletions
+1
View File
@@ -6,3 +6,4 @@ NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
@@ -117,7 +117,7 @@ const NestedDataListItem = ({
)}
</h4>
{!hasChildren && (
<code className="text-vega-light-400 mb-2 last:mb-0 dark:text-vega-dark-400 break-all">
<code className="text-vega-light-100 mb-2 last:mb-0 dark:text-vega-dark-100 break-all">
{JSON.stringify(value, null, ' ')}
</code>
)}
@@ -1,5 +1,4 @@
import { t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { StatusMessage } from '../status-message';
interface RenderFetchedProps {
@@ -0,0 +1,62 @@
import { t } from '@vegaprotocol/react-helpers';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { BlockLink } from '../../links';
import { StatusMessage } from '../../status-message';
import { ENV } from '../../../config/env';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { components } from '../../../../types/explorer';
interface TxDetailsProtocolUpgradeProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* Validator event: Protocol Upgrade proposal
*/
export const TxDetailsProtocolUpgrade = ({
txData,
pubKey,
blockData,
}: TxDetailsProtocolUpgradeProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const upgrade: components['schemas']['v1ProtocolUpgradeProposal'] =
txData.command.protocolUpgradeProposal;
if (!upgrade || !upgrade.upgradeBlockHeight || !upgrade.vegaReleaseTag) {
return (
<StatusMessage>{t('Invalid upgrade proposal format')}</StatusMessage>
);
}
const urlBase = ENV.dataSources.vegaRepoUrl;
const release = upgrade.vegaReleaseTag;
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Upgrade at block')}</TableCell>
<TableCell>
<BlockLink height={upgrade.upgradeBlockHeight} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Upgrade to')}</TableCell>
<TableCell>
<ExternalLink href={`${urlBase}${release}`}>
{upgrade.vegaReleaseTag}
</ExternalLink>
</TableCell>
</TableRow>
</TableWithTbody>
);
};
@@ -19,6 +19,7 @@ import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel';
import { TxDetailsDataSubmission } from './tx-data-submission';
import { TxProposalVote } from './tx-proposal-vote';
import { TxDetailsProtocolUpgrade } from './tx-details-protocol-upgrade';
interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -70,6 +71,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsOrder;
case 'Submit Oracle Data':
return TxDetailsDataSubmission;
case 'Protocol Upgrade':
return TxDetailsProtocolUpgrade;
case 'Cancel Order':
return TxDetailsOrderCancel;
case 'Amend Order':
@@ -1,10 +1,11 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MarketLink } from '../../links';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
export type LiquidityCancellation =
components['schemas']['v1LiquidityProvisionCancellation'];
+2
View File
@@ -10,6 +10,7 @@ const truthy = ['1', 'true'];
export const ENV = {
// Data sources
// Environment
env: windowOrDefault('NX_VEGA_ENV'),
dsn: windowOrDefault('NX_EXPLORER_SENTRY_DSN'),
dataSources: {
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
@@ -17,6 +18,7 @@ export const ENV = {
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
ethExplorerUrl: windowOrDefault('NX_ETHERSCAN_URL'),
governanceUrl: windowOrDefault('NX_VEGA_GOVERNANCE_URL'),
vegaRepoUrl: windowOrDefault('NX_VEGA_REPO_URL'),
},
flags: {
assets: truthy.includes(windowOrDefault('NX_EXPLORER_ASSETS')),
@@ -10,7 +10,7 @@ export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec',
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } } } } | null> | null } | null };
export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
@@ -73,7 +73,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
`;
export const ExplorerOracleDataConnectionFragmentDoc = gql`
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection(pagination: {first: 1}) {
dataConnection {
pageInfo {
hasNextPage
}
@@ -113,13 +113,11 @@ export const ExplorerOracleSpecsDocument = gql`
edges {
node {
...ExplorerOracleDataSource
...ExplorerOracleDataConnection
}
}
}
}
${ExplorerOracleDataSourceFragmentDoc}
${ExplorerOracleDataConnectionFragmentDoc}`;
${ExplorerOracleDataSourceFragmentDoc}`;
/**
* __useExplorerOracleSpecsQuery__
@@ -22,7 +22,7 @@ export type SourceType =
interface OracleDetailsProps {
id: string;
dataSource: ExplorerOracleDataSourceFragment;
dataConnection: ExplorerOracleDataConnectionFragment;
dataConnection?: ExplorerOracleDataConnectionFragment;
// Defaults to false. Hides the count of 'broadcasts' this oracle has seen
showBroadcasts?: boolean;
}
@@ -41,7 +41,8 @@ export const OracleDetails = ({
showBroadcasts = false,
}: OracleDetailsProps) => {
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
const reportsCount: number = dataConnection.dataConnection.edges?.length || 0;
const reportsCount: number =
dataConnection?.dataConnection.edges?.length || 0;
return (
<div>
@@ -63,7 +64,9 @@ export const OracleDetails = ({
</TableRow>
</TableWithTbody>
<OracleFilter data={dataSource} />
{showBroadcasts ? <OracleData data={dataConnection} /> : null}
{showBroadcasts && dataConnection ? (
<OracleData data={dataConnection} />
) : null}
</div>
);
};
@@ -28,7 +28,7 @@ const Oracles = () => {
<OracleDetails
id={id}
dataSource={o?.node}
dataConnection={o?.node}
showBroadcasts={false}
/>
<details>
<summary className="pointer">JSON</summary>
@@ -38,7 +38,7 @@ const Tx = () => {
to={`/${Routes.TX}`}
>
<Icon
className="text-vega-light-300 dark:text-vega-light-300"
className="text-vega-light-150 dark:text-vega-light-150"
name={IconNames.CHEVRON_LEFT}
/>
All Transactions
+457 -84
View File
@@ -65,13 +65,46 @@
}
]
},
{
"tranche_id": 52,
"tranche_start": "2024-02-01T00:00:00.000Z",
"tranche_end": "2024-08-01T00:00:00.000Z",
"total_added": "7500",
"total_removed": "0",
"locked_amount": "7500",
"deposits": [
{
"amount": "7500",
"user": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
}
],
"withdrawals": [],
"users": [
{
"address": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
"deposits": [
{
"amount": "7500",
"user": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
"tranche_id": 52,
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
}
],
"withdrawals": [],
"total_tokens": "7500",
"withdrawn_tokens": "0",
"remaining_tokens": "7500"
}
]
},
{
"tranche_id": 49,
"tranche_start": "2022-12-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "86666.297",
"total_removed": "0",
"locked_amount": "72537.154027859368347703",
"locked_amount": "71527.9874377224479678513",
"deposits": [
{
"amount": "86666.297",
@@ -137,7 +170,7 @@
"tranche_end": "2023-06-01T00:00:00.000Z",
"total_added": "2500",
"total_removed": "0",
"locked_amount": "1627.66919134106625",
"locked_amount": "1569.28784467846975",
"deposits": [
{
"amount": "2500",
@@ -331,14 +364,24 @@
"tranche_id": 42,
"tranche_start": "2023-07-01T00:00:00.000Z",
"tranche_end": "2024-01-01T00:00:00.000Z",
"total_added": "2500",
"total_added": "17500",
"total_removed": "0",
"locked_amount": "2500",
"locked_amount": "17500",
"deposits": [
{
"amount": "2500",
"user": "0x6ad69157EfB1E17EBf6614D352D098589EaE3060",
"tx": "0x0e229459260c2e74579e12e05ffd21cb4e470e3a4eacf85b7f924778b4f1c8a6"
},
{
"amount": "7500",
"user": "0xd481bCC42265089abEA41Efdb655EDCF8fF9068c",
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
},
{
"amount": "7500",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
}
],
"withdrawals": [],
@@ -357,6 +400,36 @@
"total_tokens": "2500",
"withdrawn_tokens": "0",
"remaining_tokens": "2500"
},
{
"address": "0xd481bCC42265089abEA41Efdb655EDCF8fF9068c",
"deposits": [
{
"amount": "7500",
"user": "0xd481bCC42265089abEA41Efdb655EDCF8fF9068c",
"tranche_id": 42,
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
}
],
"withdrawals": [],
"total_tokens": "7500",
"withdrawn_tokens": "0",
"remaining_tokens": "7500"
},
{
"address": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"deposits": [
{
"amount": "7500",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 42,
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
}
],
"withdrawals": [],
"total_tokens": "7500",
"withdrawn_tokens": "0",
"remaining_tokens": "7500"
}
]
},
@@ -484,8 +557,8 @@
"tranche_start": "2023-02-01T00:00:00.000Z",
"tranche_end": "2023-08-01T00:00:00.000Z",
"total_added": "37500",
"total_removed": "0",
"locked_amount": "37188.0491290669125",
"total_removed": "183.137181525",
"locked_amount": "36307.49069597912625",
"deposits": [
{
"amount": "7500",
@@ -498,7 +571,13 @@
"tx": "0x302591debd812f93121a17dd0413ae5084f3743a868b4325f81990eac58f8292"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "183.137181525",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x326ded5446d14472f79d487ece43dd7db760fec5df52e310342930db38fb5de1"
}
],
"users": [
{
"address": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -510,10 +589,17 @@
"tx": "0x1a8578ff6c2531d666b92690b7fc02e0512588dae82a44a163f25010224b6272"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "183.137181525",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 34,
"tx": "0x326ded5446d14472f79d487ece43dd7db760fec5df52e310342930db38fb5de1"
}
],
"total_tokens": "7500",
"withdrawn_tokens": "0",
"remaining_tokens": "7500"
"withdrawn_tokens": "183.137181525",
"remaining_tokens": "7316.862818475"
},
{
"address": "0x0B4e6fcE839B01ef43DA6F890FAC7B1Afb004600",
@@ -538,7 +624,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45",
"total_removed": "0",
"locked_amount": "72470.97300178364112351",
"locked_amount": "71462.72714918200044174",
"deposits": [
{
"amount": "129999.45",
@@ -604,7 +690,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "62600",
"total_removed": "0",
"locked_amount": "36444.23082825976788",
"locked_amount": "35715.29892820902776",
"deposits": [
{
"amount": "10000",
@@ -797,7 +883,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000",
"total_removed": "0",
"locked_amount": "3102.661878488077",
"locked_amount": "3044.440480720446",
"deposits": [
{
"amount": "5000",
@@ -1008,7 +1094,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "97499.58",
"total_removed": "0",
"locked_amount": "14309.4751611591714224046",
"locked_amount": "13320.4799021065353983616",
"deposits": [
{
"amount": "97499.58",
@@ -1041,7 +1127,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "135173.4239508",
"total_removed": "98230.390980249184455396",
"locked_amount": "19558.58211747851579304108696",
"locked_amount": "18206.796341262034809441102696",
"deposits": [
{
"amount": "135173.4239508",
@@ -1087,7 +1173,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "32499.86",
"total_removed": "0",
"locked_amount": "6019.749088881220610994",
"locked_amount": "5603.6958624323669808404",
"deposits": [
{
"amount": "32499.86",
@@ -1120,7 +1206,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "10833.29",
"total_removed": "0",
"locked_amount": "1959.369914168368365833",
"locked_amount": "1823.94862624421256173",
"deposits": [
{
"amount": "10833.29",
@@ -1153,7 +1239,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "22749.93",
"total_removed": "0",
"locked_amount": "7324.562356528141179063",
"locked_amount": "6818.3273364692396522472",
"deposits": [
{
"amount": "6500",
@@ -1291,8 +1377,8 @@
"tranche_start": "2022-11-01T00:00:00.000Z",
"tranche_end": "2023-05-01T00:00:00.000Z",
"total_added": "22500",
"total_removed": "3853.26264195",
"locked_amount": "10876.365389042358075",
"total_removed": "3995.28612255",
"locked_amount": "10348.030329189685875",
"deposits": [
{
"amount": "7500",
@@ -1316,6 +1402,11 @@
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x702e76a6868be327dcce3416fa163759a406b68600063473106afdb555f03db4"
},
{
"amount": "142.0234806",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x3f253311d975a353930c10981b742ac3b0df52da085d437244f077b63ec32953"
},
{
"amount": "305.3119245",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -1391,6 +1482,12 @@
"tranche_id": 33,
"tx": "0x702e76a6868be327dcce3416fa163759a406b68600063473106afdb555f03db4"
},
{
"amount": "142.0234806",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 33,
"tx": "0x3f253311d975a353930c10981b742ac3b0df52da085d437244f077b63ec32953"
},
{
"amount": "305.3119245",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -1453,8 +1550,8 @@
}
],
"total_tokens": "7500",
"withdrawn_tokens": "3853.26264195",
"remaining_tokens": "3646.73735805"
"withdrawn_tokens": "3995.28612255",
"remaining_tokens": "3504.71387745"
},
{
"address": "0x2539b51EbDE65a75672aBcfE9439a706a99D18D1",
@@ -1479,7 +1576,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38",
"total_removed": "928642.9598472029154",
"locked_amount": "635097.0330095579571042166",
"locked_amount": "612507.96463901900484135",
"deposits": [
{
"amount": "1852091.69",
@@ -33440,7 +33537,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671",
"total_removed": "589730.8667090699299",
"locked_amount": "1000424.34716318809300120353",
"locked_amount": "965712.81375596155222172624",
"deposits": [
{
"amount": "1998.95815",
@@ -34754,8 +34851,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001",
"total_removed": "553068.12935216697830452",
"locked_amount": "8847128.0872990775682462500282478416234118",
"total_removed": "557133.30560592463281452",
"locked_amount": "8724043.2185305122313058695420656514828332",
"deposits": [
{
"amount": "16249.93",
@@ -35294,6 +35391,31 @@
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x374feedb0a834a85c280bc2d0d021b727c4d7b303ac035bca381e1f1e7cd662e"
},
{
"amount": "642.55098061144375",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0xe01c0b55950dd8fbc43b42e45e486ae30b2114869a93294f66a0395d9700a157"
},
{
"amount": "1181.60422182699945",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tx": "0x0817f7d1dd8b4931d3d72c2200528a68931366595a0ab42c1f02506552ae9f02"
},
{
"amount": "983.56229747023875",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0xcc454df031cba4b8a902fb6751d904e19e53a12263d500dcfe3c0bea079c9de8"
},
{
"amount": "730.09381677206056",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tx": "0xb0d4e11c4f5aab1c4c65994c14d5272ecb4df9972ddee36ae5389de3731a3e35"
},
{
"amount": "527.364937076912",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0xa344428c2b1bf9b4685959441983da46bcd434ac0ee5ada325876c8733eba603"
},
{
"amount": "858.360074993579125",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -36895,6 +37017,24 @@
"tranche_id": 2,
"tx": "0x374feedb0a834a85c280bc2d0d021b727c4d7b303ac035bca381e1f1e7cd662e"
},
{
"amount": "642.55098061144375",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0xe01c0b55950dd8fbc43b42e45e486ae30b2114869a93294f66a0395d9700a157"
},
{
"amount": "983.56229747023875",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0xcc454df031cba4b8a902fb6751d904e19e53a12263d500dcfe3c0bea079c9de8"
},
{
"amount": "527.364937076912",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0xa344428c2b1bf9b4685959441983da46bcd434ac0ee5ada325876c8733eba603"
},
{
"amount": "858.360074993579125",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -37983,8 +38123,8 @@
}
],
"total_tokens": "259998.8875",
"withdrawn_tokens": "114813.26540279885025",
"remaining_tokens": "145185.62209720114975"
"withdrawn_tokens": "116966.74361795744475",
"remaining_tokens": "143032.14388204255525"
},
{
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
@@ -38217,6 +38357,18 @@
"tranche_id": 2,
"tx": "0x137cf963adc7f889f2b24bb3d55716cb6b8581bf13040f13e65f1d16c52e2f9f"
},
{
"amount": "1181.60422182699945",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tranche_id": 2,
"tx": "0x0817f7d1dd8b4931d3d72c2200528a68931366595a0ab42c1f02506552ae9f02"
},
{
"amount": "730.09381677206056",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tranche_id": 2,
"tx": "0xb0d4e11c4f5aab1c4c65994c14d5272ecb4df9972ddee36ae5389de3731a3e35"
},
{
"amount": "1293.67099136315494",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
@@ -38417,8 +38569,8 @@
}
],
"total_tokens": "150551.801",
"withdrawn_tokens": "65760.67703228632158",
"remaining_tokens": "84791.12396771367842"
"withdrawn_tokens": "67672.37507088538159",
"remaining_tokens": "82879.42592911461841"
},
{
"address": "0x4d982Ab0823fD2f48e934a7be2bb0a5374a26148",
@@ -40155,8 +40307,8 @@
"tranche_start": "2021-11-05T00:00:00.000Z",
"tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999",
"total_removed": "3711762.46803043411726343",
"locked_amount": "2446166.93445800685782748312503739",
"total_removed": "3920990.096830703963164282",
"locked_amount": "2332535.769515174096512550505057687",
"deposits": [
{
"amount": "129284.449",
@@ -40380,6 +40532,41 @@
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x0552a16f5a65f3ea47db290b80f164d6553717dd735e73bb0429b81e17bbbb3e"
},
{
"amount": "21866.26138234560055572",
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
"tx": "0x99ef8fa79957e58f7b84adaf383bbd1bd19f9850a8e151b9d9822dd0aa102ba2"
},
{
"amount": "892.784306759125222",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x2f991e6e03148ea102fe9c5586aa0e652e2dcc0b92a4db18edd58ba1a52eb00d"
},
{
"amount": "61457.697291116421665635",
"user": "0x35022e6c85B50F4904C7BC7e692A9F3bbEE8D2CE",
"tx": "0x04da96101bf976dd51e21e038b061c7c161100c02b4e60e0d81a3a56ab01761d"
},
{
"amount": "61457.922157658140165533",
"user": "0x534200f54E753D998F9C2984B5971e4DB490e56F",
"tx": "0x91774e0eddcd67a828be4320fd596eef753a6f216c89fe0c2c3e6407f0b44559"
},
{
"amount": "61458.259458207381442714",
"user": "0x85E14d90C63f70421a098AC26a5aCaB3854a1ee8",
"tx": "0x188c440997a39f63e688188099b8ac5ab0829e77386de239026f0152f2de4f78"
},
{
"amount": "1363.61315668407184225",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0xf85cd1afadb4ab0505bd1a1c7accfaf89e466772a241ee0990deb678334bc27b"
},
{
"amount": "731.091047499105007",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0xc73c47dcfac4093bb6325fe92986dd010d5a773af4bb97e188ad57359a309a52"
},
{
"amount": "8950.14985089483210984",
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
@@ -43199,6 +43386,24 @@
"tranche_id": 3,
"tx": "0x0552a16f5a65f3ea47db290b80f164d6553717dd735e73bb0429b81e17bbbb3e"
},
{
"amount": "892.784306759125222",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0x2f991e6e03148ea102fe9c5586aa0e652e2dcc0b92a4db18edd58ba1a52eb00d"
},
{
"amount": "1363.61315668407184225",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0xf85cd1afadb4ab0505bd1a1c7accfaf89e466772a241ee0990deb678334bc27b"
},
{
"amount": "731.091047499105007",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0xc73c47dcfac4093bb6325fe92986dd010d5a773af4bb97e188ad57359a309a52"
},
{
"amount": "1192.05386354121365675",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@@ -45589,8 +45794,8 @@
}
],
"total_tokens": "359123.469575",
"withdrawn_tokens": "298604.4155492129604925",
"remaining_tokens": "60519.0540257870395075"
"withdrawn_tokens": "301591.90406015526256375",
"remaining_tokens": "57531.56551484473743625"
},
{
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
@@ -45733,10 +45938,17 @@
"tx": "0xa1445d0e6b158cc251e40952257cc3ccbab6cb63fd82c45761148510dfc0ec61"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "61458.259458207381442714",
"user": "0x85E14d90C63f70421a098AC26a5aCaB3854a1ee8",
"tranche_id": 3,
"tx": "0x188c440997a39f63e688188099b8ac5ab0829e77386de239026f0152f2de4f78"
}
],
"total_tokens": "73666.3527333333",
"withdrawn_tokens": "0",
"remaining_tokens": "73666.3527333333"
"withdrawn_tokens": "61458.259458207381442714",
"remaining_tokens": "12208.093275125918557286"
},
{
"address": "0xcc2cf726A84e71301f9079FC177BA946aa98A694",
@@ -45813,6 +46025,12 @@
}
],
"withdrawals": [
{
"amount": "21866.26138234560055572",
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
"tranche_id": 3,
"tx": "0x99ef8fa79957e58f7b84adaf383bbd1bd19f9850a8e151b9d9822dd0aa102ba2"
},
{
"amount": "8950.14985089483210984",
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
@@ -46133,8 +46351,8 @@
}
],
"total_tokens": "1266324.603486",
"withdrawn_tokens": "1032849.1990853972736093",
"remaining_tokens": "233475.4044006027263907"
"withdrawn_tokens": "1054715.46046774287416502",
"remaining_tokens": "211609.14301825712583498"
},
{
"address": "0xC5d9221EB9c28A69859264c0A2Fe0d3272228296",
@@ -46294,10 +46512,17 @@
"tx": "0x1d497efca56500bee594a6dfbbee65b1ceb2a59a840577febfe116f465b59504"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "61457.697291116421665635",
"user": "0x35022e6c85B50F4904C7BC7e692A9F3bbEE8D2CE",
"tranche_id": 3,
"tx": "0x04da96101bf976dd51e21e038b061c7c161100c02b4e60e0d81a3a56ab01761d"
}
],
"total_tokens": "73666.3527333333",
"withdrawn_tokens": "0",
"remaining_tokens": "73666.3527333333"
"withdrawn_tokens": "61457.697291116421665635",
"remaining_tokens": "12208.655442216878334365"
},
{
"address": "0xC62BC272187e0Fd48ed126B99ABD6e7943707E64",
@@ -46391,10 +46616,17 @@
"tx": "0x1626b2d31729ad9ef3d9d2f939fcc08c3d0205319a93ad33a7c4003731357309"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "61457.922157658140165533",
"user": "0x534200f54E753D998F9C2984B5971e4DB490e56F",
"tranche_id": 3,
"tx": "0x91774e0eddcd67a828be4320fd596eef753a6f216c89fe0c2c3e6407f0b44559"
}
],
"total_tokens": "73666.3527333333",
"withdrawn_tokens": "0",
"remaining_tokens": "73666.3527333333"
"withdrawn_tokens": "61457.922157658140165533",
"remaining_tokens": "12208.430575675159834467"
},
{
"address": "0x4020DC404D9470E0F08D2E7A8d6eBc54035CF42C",
@@ -46900,8 +47132,8 @@
"tranche_start": "2021-10-05T00:00:00.000Z",
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303",
"total_removed": "2720584.477569657792162642",
"locked_amount": "649591.946558625470488911658628152",
"total_removed": "2730068.739915456784546642",
"locked_amount": "604695.586054148570133080743399363",
"deposits": [
{
"amount": "552496.6455",
@@ -47075,6 +47307,16 @@
"user": "0x6ae83EAB68b7112BaD5AfD72d6B24546AbFF137D",
"tx": "0x47eb4c897187630f558845180b019d4c5c1816efd91abf3db4964ed14eea5efd"
},
{
"amount": "5240.955854504870917",
"user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75",
"tx": "0x936296f4bc21595778c766441b849f6f5a53f5b52fded7866703d276f137ff10"
},
{
"amount": "4243.306491294121467",
"user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90",
"tx": "0x3dee2b22695a6bd2cf3925945ecac7b84cb3cf5b65113fc8af355a8fcce3c49d"
},
{
"amount": "3634.58269967002683",
"user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90",
@@ -47960,6 +48202,12 @@
}
],
"withdrawals": [
{
"amount": "5240.955854504870917",
"user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75",
"tranche_id": 4,
"tx": "0x936296f4bc21595778c766441b849f6f5a53f5b52fded7866703d276f137ff10"
},
{
"amount": "11863.408335178645863",
"user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75",
@@ -48052,8 +48300,8 @@
}
],
"total_tokens": "110499.5291",
"withdrawn_tokens": "93566.506422699926443",
"remaining_tokens": "16933.022677300073557"
"withdrawn_tokens": "98807.46227720479736",
"remaining_tokens": "11692.06682279520264"
},
{
"address": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3",
@@ -48535,6 +48783,12 @@
"tranche_id": 4,
"tx": "0x687f74292db1a9d0c79bedca56eb5dd57d5d338a2d536b355cd2d1022dc4889b"
},
{
"amount": "4243.306491294121467",
"user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90",
"tranche_id": 4,
"tx": "0x3dee2b22695a6bd2cf3925945ecac7b84cb3cf5b65113fc8af355a8fcce3c49d"
},
{
"amount": "3634.58269967002683",
"user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90",
@@ -48765,8 +49019,8 @@
}
],
"total_tokens": "331498.5873",
"withdrawn_tokens": "292494.46862336024442",
"remaining_tokens": "39004.11867663975558"
"withdrawn_tokens": "296737.775114654365887",
"remaining_tokens": "34760.812185345634113"
},
{
"address": "0x16da609341ed67750A8BCC5AAa2005471006Cd77",
@@ -48861,8 +49115,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996",
"total_removed": "31923.1301021472685",
"locked_amount": "158522.95647737743991233846067988",
"total_removed": "32361.4666889012685",
"locked_amount": "153022.71558941830041836828209032",
"deposits": [
{
"amount": "3000",
@@ -55501,6 +55755,26 @@
"user": "0x7C8D2D8BcFffcD48dBcb65C5Bb7588B66dcD22bb",
"tx": "0x2883648c0fa0f5a27defb406ab22bc0120d43e3a98b16fd7a97d99f9e79dc24c"
},
{
"amount": "133.62069381",
"user": "0xfFdd56a550Cd388071a485f9331c46BeF652eC28",
"tx": "0xd3f93705d1ddd14d480fa9e298d4f942efe21feebba8a64d1ba5bd2d01c8ceb3"
},
{
"amount": "133.64984145",
"user": "0x3616Dc96aBA2113B5E96E3B27fA61E0F0444cDe0",
"tx": "0x45a4a7b7d6ba5248f574539e60389d8f758ac92791d8e7ac1d6c6f8fbc0b9e9c"
},
{
"amount": "133.654027142",
"user": "0x7D7219bA97d4d9F5135cd7c489e407bB1EAa0A1E",
"tx": "0xcf9db8b982712a77cec727a423116897853a3180515f285d07ecc16c1d7e1634"
},
{
"amount": "37.412024352",
"user": "0x0e199b123f71f964d6567869B6B849C1f255A855",
"tx": "0x6df8666e49f7d491c740d6a36df031aac232a6cf37fa197aa1f6b2bb481be99c"
},
{
"amount": "78.261187214",
"user": "0x479F059c46c6873C6B970A92911084b3eA036d56",
@@ -62388,10 +62662,17 @@
"tx": "0x75de6ca47e0da361181d14aceb5d08e572754861465caa67a12b528886d55307"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "133.64984145",
"user": "0x3616Dc96aBA2113B5E96E3B27fA61E0F0444cDe0",
"tranche_id": 5,
"tx": "0x45a4a7b7d6ba5248f574539e60389d8f758ac92791d8e7ac1d6c6f8fbc0b9e9c"
}
],
"total_tokens": "200",
"withdrawn_tokens": "0",
"remaining_tokens": "200"
"withdrawn_tokens": "133.64984145",
"remaining_tokens": "66.35015855"
},
{
"address": "0x55795BCA099a03A3619447c351BBd1f4E20Ee15f",
@@ -62418,10 +62699,17 @@
"tx": "0x75de6ca47e0da361181d14aceb5d08e572754861465caa67a12b528886d55307"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "133.62069381",
"user": "0xfFdd56a550Cd388071a485f9331c46BeF652eC28",
"tranche_id": 5,
"tx": "0xd3f93705d1ddd14d480fa9e298d4f942efe21feebba8a64d1ba5bd2d01c8ceb3"
}
],
"total_tokens": "200",
"withdrawn_tokens": "0",
"remaining_tokens": "200"
"withdrawn_tokens": "133.62069381",
"remaining_tokens": "66.37930619"
},
{
"address": "0x7D7219bA97d4d9F5135cd7c489e407bB1EAa0A1E",
@@ -62433,10 +62721,17 @@
"tx": "0x75de6ca47e0da361181d14aceb5d08e572754861465caa67a12b528886d55307"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "133.654027142",
"user": "0x7D7219bA97d4d9F5135cd7c489e407bB1EAa0A1E",
"tranche_id": 5,
"tx": "0xcf9db8b982712a77cec727a423116897853a3180515f285d07ecc16c1d7e1634"
}
],
"total_tokens": "200",
"withdrawn_tokens": "0",
"remaining_tokens": "200"
"withdrawn_tokens": "133.654027142",
"remaining_tokens": "66.345972858"
},
{
"address": "0x31410FE9199081BAA0a119E2c78Ecd42ca5130ce",
@@ -72609,6 +72904,12 @@
}
],
"withdrawals": [
{
"amount": "37.412024352",
"user": "0x0e199b123f71f964d6567869B6B849C1f255A855",
"tranche_id": 5,
"tx": "0x6df8666e49f7d491c740d6a36df031aac232a6cf37fa197aa1f6b2bb481be99c"
},
{
"amount": "32.098173516",
"user": "0x0e199b123f71f964d6567869B6B849C1f255A855",
@@ -72659,8 +72960,8 @@
}
],
"total_tokens": "400",
"withdrawn_tokens": "232.628602232",
"remaining_tokens": "167.371397768"
"withdrawn_tokens": "270.040626584",
"remaining_tokens": "129.959373416"
},
{
"address": "0x74Da54F44975a1C224ABb5CAB8e5fc38a268B425",
@@ -77393,7 +77694,7 @@
"tranche_start": "2021-12-05T00:00:00.000Z",
"tranche_end": "2022-06-05T00:00:00.000Z",
"total_added": "171288.42",
"total_removed": "64726.1049690697989",
"total_removed": "65326.1049690697989",
"locked_amount": "0",
"deposits": [
{
@@ -81633,6 +81934,21 @@
"user": "0x68091918EaDB98f54E8fC179870aB6F755736174",
"tx": "0x6957558e2c90b17cd1ef8673ca4a6c949dfcdf0772e3b580e7dda2655b647c64"
},
{
"amount": "250",
"user": "0x398596bD9Bb33db205B84744DFB20B6Ffbab20a3",
"tx": "0x84eb91957cae169a0d11ed3205f39642ed71d8d185bd505ad80a02c8af48742d"
},
{
"amount": "250",
"user": "0x7D7219bA97d4d9F5135cd7c489e407bB1EAa0A1E",
"tx": "0x5dad4ba5a483a5807481d36612ff51aad8a99d4381a99674f3cf10d7b0936af3"
},
{
"amount": "100",
"user": "0x15E1A3cA96D92e621cdFb305fF86672A91309652",
"tx": "0xbe66b488288b3c8422f192bdeb196e84361215b5125257e57c30cb3568bd6d3e"
},
{
"amount": "250",
"user": "0xbd09687340A09BeB0B5EE0D3C2bCa8d78eBF6E63",
@@ -88782,10 +89098,17 @@
"tx": "0x75de6ca47e0da361181d14aceb5d08e572754861465caa67a12b528886d55307"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0x398596bD9Bb33db205B84744DFB20B6Ffbab20a3",
"tranche_id": 6,
"tx": "0x84eb91957cae169a0d11ed3205f39642ed71d8d185bd505ad80a02c8af48742d"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x52D4356fb92e1b42E7eb00e3d146e71715D6a3B8",
@@ -88812,10 +89135,17 @@
"tx": "0x75de6ca47e0da361181d14aceb5d08e572754861465caa67a12b528886d55307"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0x7D7219bA97d4d9F5135cd7c489e407bB1EAa0A1E",
"tranche_id": 6,
"tx": "0x5dad4ba5a483a5807481d36612ff51aad8a99d4381a99674f3cf10d7b0936af3"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x9B99271c944B54e5cCc646526B990a34570A2660",
@@ -89095,10 +89425,17 @@
"tx": "0xc7dd4c2b995cc486fcd8b7892cd79f8fb393ada004dc68cf66ec82d99b35763c"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "100",
"user": "0x15E1A3cA96D92e621cdFb305fF86672A91309652",
"tranche_id": 6,
"tx": "0xbe66b488288b3c8422f192bdeb196e84361215b5125257e57c30cb3568bd6d3e"
}
],
"total_tokens": "100",
"withdrawn_tokens": "0",
"remaining_tokens": "100"
"withdrawn_tokens": "100",
"remaining_tokens": "0"
},
{
"address": "0x460d9FAC558e889A62670DE75f42602BEfd5352D",
@@ -131324,7 +131661,7 @@
"tranche_start": "2021-11-05T00:00:00.000Z",
"tranche_end": "2021-11-05T00:00:00.000Z",
"total_added": "2576065.7689906998",
"total_removed": "1693544.623694831",
"total_removed": "1732544.457494831",
"locked_amount": "0",
"deposits": [
{
@@ -131534,6 +131871,21 @@
}
],
"withdrawals": [
{
"amount": "12999.9446",
"user": "0x35022e6c85B50F4904C7BC7e692A9F3bbEE8D2CE",
"tx": "0xe75811dc3d3bcb569fffd47a7639a0789fbb7fdf76fe07be66b80c34cac48476"
},
{
"amount": "12999.9446",
"user": "0x534200f54E753D998F9C2984B5971e4DB490e56F",
"tx": "0xcfa366efafa255d261b11a868fea469454003be965aa279e6e605f912da55a36"
},
{
"amount": "12999.9446",
"user": "0x85E14d90C63f70421a098AC26a5aCaB3854a1ee8",
"tx": "0x1e875503f81ad9c687c06338ae24cf6e307d3ab1a0407f3b3580134aa5254d66"
},
{
"amount": "12187.4480625",
"user": "0x4020DC404D9470E0F08D2E7A8d6eBc54035CF42C",
@@ -132036,10 +132388,17 @@
"tx": "0xe2f38513b930cc0039c228a3f2874b14e0ff51ff8665fc85497e312cf2a7cc1f"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "12999.9446",
"user": "0x534200f54E753D998F9C2984B5971e4DB490e56F",
"tranche_id": 8,
"tx": "0xcfa366efafa255d261b11a868fea469454003be965aa279e6e605f912da55a36"
}
],
"total_tokens": "12999.9446",
"withdrawn_tokens": "0",
"remaining_tokens": "12999.9446"
"withdrawn_tokens": "12999.9446",
"remaining_tokens": "0"
},
{
"address": "0x91715128a71c9C734CDC20E5EdEEeA02E72e428E",
@@ -132280,10 +132639,17 @@
"tx": "0x28f48e77f8836299841824f7a0bcd610aeb626ab2dc1fdea9295989ab13e69d0"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "12999.9446",
"user": "0x85E14d90C63f70421a098AC26a5aCaB3854a1ee8",
"tranche_id": 8,
"tx": "0x1e875503f81ad9c687c06338ae24cf6e307d3ab1a0407f3b3580134aa5254d66"
}
],
"total_tokens": "12999.9446",
"withdrawn_tokens": "0",
"remaining_tokens": "12999.9446"
"withdrawn_tokens": "12999.9446",
"remaining_tokens": "0"
},
{
"address": "0x37fBC48109c4D71Bb0FFD44Bef0FE493b9Ff582a",
@@ -132310,10 +132676,17 @@
"tx": "0x8d2d46bb9e5aff5dde2d68a98adc25efaaa35742cb22299db3c3ea01b8725516"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "12999.9446",
"user": "0x35022e6c85B50F4904C7BC7e692A9F3bbEE8D2CE",
"tranche_id": 8,
"tx": "0xe75811dc3d3bcb569fffd47a7639a0789fbb7fdf76fe07be66b80c34cac48476"
}
],
"total_tokens": "12999.9446",
"withdrawn_tokens": "0",
"remaining_tokens": "12999.9446"
"withdrawn_tokens": "12999.9446",
"remaining_tokens": "0"
},
{
"address": "0x4020DC404D9470E0F08D2E7A8d6eBc54035CF42C",
+1 -1
View File
@@ -16,7 +16,6 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
#Test configuration variables
CYPRESS_FAIRGROUND=false
CYPRESS_VEGA_URL=http://localhost:3028/query
CYPRESS_VEGA_WALLET_API_TOKEN=jpeAkxcffzTLCzBX2m5TZIp3hF500YZhHwESwNKOGksdGPXeeIznXypaDfpNe2M9
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
@@ -29,3 +28,4 @@ CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f4864
CYPRESS_VEGA_TOKEN_URL=https://token.fairground.wtf
CYPRESS_VEGA_URL=http://localhost:3028/query
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
CYPRESS_VEGA_WALLET_API_TOKEN=
@@ -37,8 +37,8 @@ context(
function () {
cy.reload();
cy.wait_for_spinner();
cy.connectVegaWallet();
cy.ethereum_wallet_connect();
cy.connectVegaWallet();
cy.vega_wallet_teardown();
cy.navigate_to('validators');
}
@@ -57,7 +57,16 @@ context(
//0005-ETXN-006
//0005-ETXN-003
//0005-ETXN-005
cy.staking_page_associate_tokens('2');
cy.staking_page_associate_tokens('2', { skipConfirmation: true });
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '0.00');
cy.validate_wallet_currency('Pending association', '2.00');
cy.validate_wallet_currency('Total associated after pending', '2.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
// 0005-ETXN-002
cy.get(ethWalletAssociatedBalances, txTimeout)
@@ -98,6 +107,15 @@ context(
cy.staking_page_disassociate_tokens('2');
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '2.00');
cy.validate_wallet_currency('Pending association', '2.00');
cy.validate_wallet_currency('Total associated after pending', '0.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.get(ethWalletAssociatedBalances, txTimeout).should('not.exist');
cy.get(ethWalletTotalAssociatedBalance, txTimeout)
@@ -194,7 +212,19 @@ context(
// 1004-ASSO-024
// 1004-ASSO-023
cy.staking_page_associate_tokens('2', { type: 'contract' });
cy.staking_page_associate_tokens('2', {
type: 'contract',
skipConfirmation: true,
});
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '0.00');
cy.validate_wallet_currency('Pending association', '2.00');
cy.validate_wallet_currency('Total associated after pending', '2.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.get(ethWalletAssociatedBalances, txTimeout)
.contains(vegaWalletPublicKeyShort)
@@ -210,7 +240,19 @@ context(
});
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
cy.staking_page_disassociate_tokens('1', { type: 'contract' });
cy.staking_page_disassociate_tokens('1', {
type: 'contract',
skipConfirmation: true,
});
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '2.00');
cy.validate_wallet_currency('Pending association', '1.00');
cy.validate_wallet_currency('Total associated after pending', '1.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.get(ethWalletAssociatedBalances, txTimeout)
.contains(vegaWalletPublicKeyShort)
@@ -286,6 +328,35 @@ context(
});
// 1004-ASSO-004
it('Pending association outside of app is shown', function () {
cy.vega_wallet_associate('2');
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '0.00');
cy.validate_wallet_currency('Pending association', '2.00');
cy.validate_wallet_currency('Total associated after pending', '2.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.validate_wallet_currency('Associated', '2.00');
});
it('Disassociation outside of app is shown', function () {
cy.staking_page_associate_tokens('2');
cy.validate_wallet_currency('Associated', '2.00');
cy.vega_wallet_disassociate('2');
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '2.00');
cy.validate_wallet_currency('Pending association', '2.00');
cy.validate_wallet_currency('Total associated after pending', '0.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.validate_wallet_currency('Associated', '0.00');
});
it('Able to associate tokens to different public key of connected vega wallet', function () {
cy.get(ethWalletAssociateButton).first().click();
cy.get(associateWalletRadioButton).click();
@@ -310,6 +381,7 @@ context(
'vegaWalletPublicKey2Short'
)} can now participate in governance and nominate a validator with your associated $VEGA.`
);
cy.staking_page_disassociate_all_tokens();
});
});
}
@@ -321,6 +321,8 @@ context(
'200000',
vegaWalletPublicKey
);
cy.reload();
cy.wait_for_spinner();
cy.connectVegaWallet();
cy.ethereum_wallet_connect();
});
+31 -10
View File
@@ -58,6 +58,8 @@ Cypress.Commands.add('staking_validator_page_remove_stake', (stake) => {
Cypress.Commands.add('staking_page_associate_tokens', (amount, options) => {
let approve = options && options.approve ? options.approve : false;
let type = options && options.type ? options.type : 'wallet';
let skipConfirmation =
options && options.skipConfirmation ? options.skipConfirmation : false;
cy.highlight(`Associating ${amount} tokens from ${type}`);
cy.get(ethWalletAssociateButton).first().click();
@@ -79,16 +81,19 @@ Cypress.Commands.add('staking_page_associate_tokens', (amount, options) => {
);
}
cy.get(tokenSubmitButton, txTimeout).should('be.enabled').click();
cy.contains(
`Associating with Vega key. Waiting for ${Cypress.env(
'blockConfirmations'
)} more confirmations..`,
txTimeout
).should('be.visible');
cy.contains(
'can now participate in governance and nominate a validator',
txTimeout
).should('be.visible');
if (!skipConfirmation) {
cy.contains(
`Associating with Vega key. Waiting for ${Cypress.env(
'blockConfirmations'
)} more confirmations..`,
txTimeout
).should('be.visible');
cy.contains(
'can now participate in governance and nominate a validator',
txTimeout
).should('be.visible');
}
});
Cypress.Commands.add('staking_page_disassociate_tokens', (amount, options) => {
@@ -216,3 +221,19 @@ Cypress.Commands.add('close_staking_dialog', () => {
cy.get('a').should('have.text', 'Back to Staking').click();
});
});
Cypress.Commands.add(
'validate_wallet_currency',
(currencyTitle, expectedAmount) => {
cy.get("[data-testid='currency-title']")
.contains(currencyTitle)
.parent()
.parent()
.within(() => {
cy.getByTestId('currency-value', txTimeout).should(
'have.text',
expectedAmount
);
});
}
);
@@ -8,7 +8,6 @@ import {
import { ethers, Wallet } from 'ethers';
const vegaWalletContainer = '[data-testid="vega-wallet"]';
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
const vegaWalletMnemonic = Cypress.env('vegaWalletMnemonic');
const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey');
const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
@@ -43,6 +42,11 @@ before('Vega wallet teardown prep', function () {
});
});
beforeEach(function () {
cy.wrap(this.stakingBridgeContract).as('stakingBridgeContract');
cy.wrap(this.vestingContract).as('vestingContract');
});
Cypress.Commands.add('deposit_asset', function (assetEthAddress) {
cy.get('@signer', { log: false }).then((signer) => {
// Approve asset
@@ -91,29 +95,18 @@ Cypress.Commands.add('faucet_asset', function (assetEthAddress) {
});
Cypress.Commands.add('vega_wallet_teardown', function () {
cy.get('body').then(($body) => {
if ($body.find('[data-testid="eth-wallet-associated-balances"]').length) {
cy.vega_wallet_teardown_vesting(this.vestingContract);
cy.vega_wallet_teardown_staking(this.stakingBridgeContract);
}
});
cy.get(vegaWalletContainer).within(() => {
cy.get(vegaWalletAssociatedBalance)
.first()
.invoke('text')
.then((balance) => {
if (balance != '0.00') {
cy.vega_wallet_teardown_vesting(this.vestingContract);
cy.vega_wallet_teardown_staking(this.stakingBridgeContract);
}
});
cy.get('[data-testid="associated-amount"]', { timeout: 30000 }).should(
'contain.text',
'0.00'
);
});
cy.get(vegaWalletContainer).within(() => {
cy.get(vegaWalletAssociatedBalance, { timeout: transactionTimeout }).should(
'contain',
'0.00',
{ timeout: transactionTimeout }
);
});
});
Cypress.Commands.add(
@@ -176,6 +169,22 @@ Cypress.Commands.add('vega_wallet_teardown_vesting', (vestingContract) => {
});
});
Cypress.Commands.add('vega_wallet_associate', (amount) => {
amount = amount + '0'.repeat(18);
cy.highlight('Associating tokens');
cy.get('@stakingBridgeContract').then((stakingBridgeContract) => {
stakingBridgeContract.stake(amount, vegaWalletPubKey);
});
});
Cypress.Commands.add('vega_wallet_disassociate', (amount) => {
amount = amount + '0'.repeat(18);
cy.highlight('Disassociating tokens');
cy.get('@stakingBridgeContract').then((stakingBridgeContract) => {
stakingBridgeContract.remove_stake(amount, vegaWalletPubKey);
});
});
Cypress.Commands.add('wait_for_transaction', (tx) => {
cy.wrap(tx.wait(1).catch(cy.log), { timeout: transactionTimeout });
});
+3 -2
View File
@@ -8,6 +8,7 @@ import { AppLoader } from './app-loader';
import { NetworkInfo } from '@vegaprotocol/network-info';
import { BalanceManager } from './components/balance-manager';
import { EthWallet } from './components/eth-wallet';
import { AppLayout } from './components/page-templates/app-layout';
import { TemplateSidebar } from './components/page-templates/template-sidebar';
import { TransactionModal } from './components/transactions-modal';
import { VegaWallet } from './components/vega-wallet';
@@ -88,14 +89,14 @@ const Web3Container = ({
<AppLoader>
<BalanceManager>
<>
<div className="app w-full max-w-[1500px] mx-auto grid grid-rows-[min-content_min-content_1fr_min-content] min-h-full border-neutral-700 lg:border-l lg:border-r lg:text-body-large">
<AppLayout>
<TemplateSidebar sidebar={sideBar}>
<AppRouter />
</TemplateSidebar>
<footer className="p-4 border-t border-neutral-700">
<NetworkInfo />
</footer>
</div>
</AppLayout>
<VegaWalletDialogs />
<TransactionModal />
<WithdrawalDialog />
@@ -18,7 +18,7 @@ export const NavDropDown = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
<AppNavLink
name={
<NavDropdownMenuTrigger
className="w-auto text-capMenu"
className="w-auto flex items-center"
data-testid="state-trigger"
onClick={() => setOpen(!isOpen)}
>
@@ -38,6 +38,7 @@ export const NavDropDown = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
name={t(r.name)}
path={r.path}
navbarTheme={'inherit'}
subNav={true}
end={true}
fullWidth={true}
/>
+12 -7
View File
@@ -13,6 +13,7 @@ interface AppNavLinkProps {
target?: HTMLAttributeAnchorTarget;
end?: boolean;
fullWidth?: boolean;
subNav?: boolean;
}
export const AppNavLink = ({
@@ -23,28 +24,32 @@ export const AppNavLink = ({
testId,
end = false,
fullWidth = false,
subNav = false,
}: AppNavLinkProps) => {
const borderClasses = classNames('absolute h-1 w-full bottom-[-1px] left-0', {
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
'bg-black': navbarTheme === 'yellow',
});
const borderClasses = classNames(
'absolute h-0.5 w-full bottom-[-1px] left-0',
{
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
'bg-black': navbarTheme === 'yellow',
}
);
return (
<NavLink
key={path}
data-testid={testId}
to={{ pathname: path }}
className={getNavLinkClassNames(navbarTheme, fullWidth)}
className={getNavLinkClassNames(navbarTheme, fullWidth, subNav)}
target={target}
end={end}
>
{({ isActive }) => {
return (
<>
<div className={subNav ? 'inline-block relative pb-1' : undefined}>
{name}
{isActive && (
<span data-testid="link-active" className={borderClasses} />
)}
</>
</div>
);
}}
</NavLink>
+1 -2
View File
@@ -22,9 +22,8 @@ const renderComponent = (initialEntries?: string[]) => {
};
describe('nav', () => {
it('Renders title and logo with link to home', () => {
it('Renders logo with link to home', () => {
renderComponent();
expect(screen.getByText('Governance')).toBeInTheDocument();
expect(screen.getByTestId('logo-link')).toHaveProperty(
'href',
'http://localhost/'
+10 -4
View File
@@ -3,7 +3,8 @@ import { NetworkSwitcher } from '@vegaprotocol/environment';
import { useEffect, useState } from 'react';
import { TOP_LEVEL_ROUTES } from '../../routes/routes';
import { useTranslation } from 'react-i18next';
import vegaWhite from '../../images/vega_white.png';
import logoWhiteText from '../../images/logo-white-text.png';
import logoBlackText from '../../images/logo-black-text.png';
import debounce from 'lodash/debounce';
import { NavDrawer } from './nav-draw';
import { Nav as ToolkitNav } from '@vegaprotocol/ui-toolkit';
@@ -46,14 +47,19 @@ export const Nav = ({ navbarTheme = 'inherit' }: NavbarProps) => {
navbarTheme={navbarTheme}
icon={
<Link to="/" data-testid="logo-link">
<img alt="Vega" src={vegaWhite} height={30} width={30} />
<img
alt="Vega"
src={navbarTheme === 'yellow' ? logoBlackText : logoWhiteText}
height={30}
width={250}
/>
</Link>
}
title={t('Governance')}
title={undefined}
titleContent={<NetworkSwitcher />}
>
{isDesktop ? (
<nav className="flex items-center flex-1 px-2">
<nav className="flex items-center flex-1 px-4">
{TOP_LEVEL_ROUTES.map((r) => (
<AppNavLink
key={r.path}
@@ -0,0 +1,21 @@
import classNames from 'classnames';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { ReactNode } from 'react';
interface AppLayoutProps {
children: ReactNode;
}
export const AppLayout = ({ children }: AppLayoutProps) => {
const { isReadOnly } = useVegaWallet();
const AppLayoutClasses = classNames(
'app w-full max-w-[1500px] mx-auto grid min-h-full',
'border-neutral-700 lg:border-l lg:border-r',
'lg:text-body-large',
{
'grid-rows-[repeat(2,min-content)_1fr_min-content]': !isReadOnly,
'grid-rows-[repeat(3,min-content)_1fr_min-content]': isReadOnly,
}
);
return <div className={AppLayoutClasses}>{children}</div>;
};
@@ -30,9 +30,7 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
<Nav navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
{isReadOnly ? (
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
) : (
<div />
)}
) : null}
<div className="w-full border-b border-neutral-700 lg:grid lg:grid-rows-[1fr] lg:grid-cols-[1fr_450px]">
<main className="col-start-1 p-4">{children}</main>
<aside className="col-start-2 row-start-1 row-span-2 hidden lg:block p-4 bg-banner bg-contain border-l border-neutral-700">
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -177,7 +177,7 @@ interface TotalPenaltiesRendererProps {
performanceScore: string;
performancePenalty: string;
overstakedAmount: string;
overstakedPenalty: string;
overstakingPenalty: string;
totalPenalties: string;
};
}
@@ -192,20 +192,10 @@ export const TotalPenaltiesRenderer = ({
description={
<>
<div>
<span>
{t('performancePenalty')}: {data.performancePenalty}
</span>
<span className="pl-2">
({t('score')} {data.performanceScore})
</span>
{t('performancePenalty')}: {data.performancePenalty}
</div>
<div>
<span>
{t('overstakedPenalty')}: {data.overstakedPenalty}
</span>
<span className="pl-2">
({t('overstaked')} {data.overstakedAmount})
</span>
{t('overstakedPenalty')}: {data.overstakingPenalty}
</div>
<div>
{t('totalPenalties')}:{' '}
+6
View File
@@ -13,3 +13,9 @@
@apply mb-2 text-neutral-400;
}
}
@layer components {
.border-default {
@apply border-vega-dark-200;
}
}
+186 -63
View File
@@ -24,7 +24,10 @@ const txTimeout = Cypress.env('txTimeout');
const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
const btcName =
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC';
const vegaName =
'Vegab4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b - VEGA';
const btcSymbol = 'tBTC';
const vegaSymbol = 'VEGA';
const usdcSymbol = 'fUSDC';
const toastContent = 'toast-content';
const ordersTab = 'Orders';
@@ -33,9 +36,112 @@ const toastCloseBtn = 'toast-close';
const price = '390';
const size = '0.0005';
const newPrice = '200';
const completeWithdrawalBtn = 'complete-withdrawal';
// TODO: ensure this test runs only if capsule is running via workflow
// Because the tests are run on a live network to optimize time, the tests are interdependent and must be run in the given order.
describe('capsule - without MultiSign', { tags: '@slow' }, () => {
before(() => {
cy.createMarket();
cy.get('@markets').then((markets) => {
cy.wrap(markets[0]).as('market');
});
});
beforeEach(() => {
cy.setVegaWallet();
});
it('can deposit', function () {
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
// 1001-DEPO-001
// 1001-DEPO-002
// 1001-DEPO-003
// 1001-DEPO-005
// 1001-DEPO-006
// 1001-DEPO-007
// 1001-DEPO-008
// 1001-DEPO-009
// 1001-DEPO-010
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.getByTestId('deposit-approve-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
cy.get(amountField).clear().type('10');
cy.getByTestId('deposit-submit').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
`Transaction confirmedYour transaction has been confirmed.View on EtherscanDeposit 10.00 ${btcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId('Collateral').click();
cy.highlight('deposit verification');
cy.getByTestId('asset', txTimeout).should('contain.text', btcSymbol);
cy.getByTestId(depositsTab).click();
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
cy.get('[col-id="txHash"]')
.should('have.length.above', 2)
.eq(1)
.parent()
.within(() => {
cy.get('[col-id="asset.symbol"]').should('have.text', btcSymbol);
cy.get('[col-id="amount"]').should('have.text', '10.00');
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Finalized');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
});
it('can not withdrawal because of no MultiSign', function () {
// 1002-WITH-022
// 1002-WITH-023
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Funds unlocked'
);
cy.getByTestId('tab-withdrawals').within(() => {
cy.get('.ag-center-cols-container')
.children()
.first()
.within(() => {
cy.get('[col-id="status"]').should('contain.text', 'Pending');
});
});
cy.highlight('withdrawals verification');
cy.getByTestId('toast-complete-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Error occurredprocessing response error'
);
cy.getByTestId(completeWithdrawalBtn).should(
'contain.text',
'Complete withdrawal'
);
});
});
describe('capsule', { tags: '@slow' }, () => {
before(() => {
cy.createMarket();
@@ -128,13 +234,15 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId('dialog-title').should('contain.text', 'Edit order');
cy.get('#limitPrice').focus().clear().type(newPrice);
cy.getByTestId('edit-order').find('[type="submit"]').click();
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(ordersTab).click();
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId(ordersTab).click();
cy.get('.ag-center-cols-container')
.children()
.first()
@@ -164,62 +272,8 @@ describe('capsule', { tags: '@slow' }, () => {
.should('contain.text', OrderStatusMapping.STATUS_CANCELLED);
});
it('can deposit', function () {
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
// 1001-DEPO-001
// 1001-DEPO-002
// 1001-DEPO-003
// 1001-DEPO-005
// 1001-DEPO-006
// 1001-DEPO-007
// 1001-DEPO-008
// 1001-DEPO-009
// 1001-DEPO-010
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.getByTestId('deposit-approve-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
cy.get(amountField).clear().type('1');
cy.getByTestId('deposit-submit').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
`Transaction confirmedYour transaction has been confirmed.View on EtherscanDeposit 1.00 ${btcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId('Collateral').click();
cy.highlight('deposit verification');
cy.getByTestId('asset', txTimeout).should('contain.text', btcSymbol);
cy.getByTestId(depositsTab).click();
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
cy.get('[col-id="txHash"]')
.should('have.length.above', 2)
.eq(1)
.parent()
.within(() => {
cy.get('[col-id="asset.symbol"]').should('have.text', btcSymbol);
cy.get('[col-id="amount"]').should('have.text', '1.00');
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Finalized');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
});
it('can withdrawal', function () {
// 1002-WITH-001
// 1002-WITH-0014
// 1002-WITH-006
// 1002-WITH-009
// 1002-WITH-011
@@ -229,14 +283,18 @@ describe('capsule', { tags: '@slow' }, () => {
// 1002-WITH-014
// 1002-WITH-015
// 1002-WITH-016
// 1002-WITH-017
// 1002-WITH-019
// 1002-WITH-020
// 1002-WITH-021
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
cy.get(assetSelectField, txTimeout).select(
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC',
{ force: true }
);
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -255,12 +313,13 @@ describe('capsule', { tags: '@slow' }, () => {
cy.highlight('withdrawals verification');
cy.getByTestId('toast-complete-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Transaction confirmed'
);
cy.getByTestId('complete-withdrawal', txTimeout).should('not.exist');
cy.getByTestId(completeWithdrawalBtn).eq(0, txTimeout).should('not.exist');
cy.get('[col-id="txHash"]', txTimeout)
.should('have.length.above', 1)
@@ -281,9 +340,12 @@ describe('capsule', { tags: '@slow' }, () => {
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
// comment because of bug #2819
// cy.getByTestId('withdraw-dialog-button').click();
// cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '0')
});
it('deposit - if approved amount is less than deposit: must see that an approval is needed and be prompted to approve more', function () {
it('approved amount is less than deposit', function () {
// 1001-DEPO-006
cy.getByTestId(depositsTab).click();
@@ -292,6 +354,67 @@ describe('capsule', { tags: '@slow' }, () => {
cy.get(amountField).clear().type('20000000');
cy.getByTestId('deposit-approve-submit').should('be.visible');
});
it('withdraw - delay verification', function () {
// 1001-DEPO-007
// 1001-DEPO-024
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
cy.getByTestId('deposit-approve-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
cy.get(amountField).clear().type('10000');
cy.getByTestId('deposit-submit').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
`Your transaction has been confirmed.`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId('Collateral').click();
cy.highlight('deposit verification');
cy.getByTestId('asset', txTimeout).should('contain.text', vegaSymbol);
cy.getByTestId(depositsTab).click();
cy.get('.ag-cell-value', txTimeout).should('contain.text', vegaSymbol);
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
cy.get('[col-id="txHash"]')
.should('have.length.above', 2)
.eq(1)
.parent()
.within(() => {
cy.get('[col-id="asset.symbol"]').should('have.text', vegaSymbol);
cy.get('[col-id="amount"]').should('have.text', '10,000.00');
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Finalized');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
cy.get(amountField).clear().type('10000');
cy.getByTestId('DELAY_TIME_value').should('have.text', '5 days');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Your funds have been unlocked'
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId(completeWithdrawalBtn).first().should('be.visible').click();
cy.getByTestId(toastContent, txTimeout).should('contain.text', 'Delayed');
});
});
function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) {
@@ -4,6 +4,7 @@ const marketInfoBtn = 'Info';
const row = 'key-value-table-row';
const marketTitle = 'accordion-title';
const externalLink = 'external-link';
const accordionContent = 'accordion-content';
describe('market info is displayed', { tags: '@smoke' }, () => {
before(() => {
@@ -179,7 +180,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
'termination.BTC.value'
);
cy.getByTestId(externalLink)
cy.getByTestId(accordionContent)
.find(`[data-testid="${externalLink}"]`)
.should('have.attr', 'href')
.and('contain', '/oracles');
});
@@ -187,12 +189,14 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
it('proposal displayed', () => {
cy.getByTestId(marketTitle).contains('Proposal').click();
cy.getByTestId(externalLink)
cy.getByTestId(accordionContent)
.find(`[data-testid="${externalLink}"]`)
.first()
.should('have.text', 'View governance proposal')
.and('have.attr', 'href')
.and('contain', '/proposals/market-0');
cy.getByTestId(externalLink)
cy.getByTestId(accordionContent)
.find(`[data-testid="${externalLink}"]`)
.eq(1)
.should('have.text', 'Propose a change to market')
.and('have.attr', 'href')
@@ -104,7 +104,8 @@ describe('markets table', { tags: '@smoke' }, () => {
'have.length',
10
);
cy.getByTestId('external-link')
cy.getByTestId('tab-proposed-markets')
.find('[data-testid="external-link"]')
.should('have.length', 11)
.last()
.should('have.text', 'Propose a new market')
@@ -41,6 +41,7 @@ describe('withdraw form validation', { tags: '@smoke' }, () => {
cy.get(toAddressField).should('have.value', ethAddressValue);
});
it('min amount', () => {
// 1002-WITH-010
selectAsset(ASSET_SEPOLIA_TBTC);
cy.get(amountField).clear().type('0');
cy.getByTestId(submitWithdrawBtn).click();
@@ -50,6 +51,8 @@ describe('withdraw form validation', { tags: '@smoke' }, () => {
);
});
it('max amount', () => {
// 1002-WITH-005
// 1002-WITH-008
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
cy.get(amountField).clear().type('1001', { delay: 100 });
cy.getByTestId(submitWithdrawBtn).click();
@@ -60,6 +63,7 @@ describe('withdraw form validation', { tags: '@smoke' }, () => {
});
it('can set amount using use maximum button', () => {
// 1002-WITH-004
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(useMaximumAmount).click();
cy.get(amountField).should('have.value', '1000.00000');
@@ -68,6 +72,9 @@ describe('withdraw form validation', { tags: '@smoke' }, () => {
describe('withdraw actions', { tags: '@regression' }, () => {
// this is extremely ugly hack, but setting it properly in contract is too much effort for such simple validation
// 1002-WITH-018
const withdrawalThreshold =
Cypress.env('VEGA_ENV') === 'CUSTOM' ? '0.00' : '100.00';
before(() => {
@@ -88,6 +95,8 @@ describe('withdraw actions', { tags: '@regression' }, () => {
});
it('triggers transaction when submitted', () => {
// 1002-WITH-002
// 1002-WITH-003
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId('BALANCE_AVAILABLE_label').should(
'contain.text',
@@ -108,7 +117,4 @@ describe('withdraw actions', { tags: '@regression' }, () => {
cy.getByTestId(submitWithdrawBtn).click();
cy.getByTestId('toast').should('contain.text', 'Awaiting confirmation');
});
it.skip('creates a withdrawal on submit'); // Needs capsule
it.skip('creates a withdrawal on submit and prompts to complete withdrawal'); // Needs capsule
});
+1
View File
@@ -8,3 +8,4 @@ NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
+1
View File
@@ -9,6 +9,7 @@ NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
+1
View File
@@ -8,3 +8,4 @@ NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
+1
View File
@@ -7,3 +7,4 @@ NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\",\"TESTNET\":\"h
NX_VEGA_TOKEN_URL=https://token.vega.xyz
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
+2 -1
View File
@@ -7,4 +7,5 @@ NX_VEGA_EXPLORER_URL=https://mainnet-mirror.explorer.vega.xyz
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"STAGNET3\":\"https://stagnet3.console.vega.xyz\"}
NX_VEGA_TOKEN_URL=https://mainnet-mirror.token.vega.xyz
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
+1
View File
@@ -7,3 +7,4 @@ NX_VEGA_NETWORKS={\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET3\":\"https
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/sandbox-network.json
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
+1
View File
@@ -8,3 +8,4 @@ NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"
NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
+1
View File
@@ -8,3 +8,4 @@ NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
+1
View File
@@ -8,3 +8,4 @@ NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
+13
View File
@@ -0,0 +1,13 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/validator-testnet-network.json
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
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_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
@@ -5,7 +5,7 @@ import { useWithdrawalDialog } from '@vegaprotocol/withdraws';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { AccountManager } from '@vegaprotocol/accounts';
import { AccountManager, useTransferDialog } from '@vegaprotocol/accounts';
import { useDepositDialog } from '@vegaprotocol/deposits';
export const AccountsContainer = () => {
@@ -13,6 +13,7 @@ export const AccountsContainer = () => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const openWithdrawalDialog = useWithdrawalDialog((store) => store.open);
const openDepositDialog = useDepositDialog((store) => store.open);
const openTransferDialog = useTransferDialog((store) => store.open);
const onClickAsset = useCallback(
(assetId?: string) => {
@@ -41,7 +42,10 @@ export const AccountsContainer = () => {
/>
</div>
{!isReadOnly && (
<div className="flex justify-end p-2 px-[11px]">
<div className="flex gap-2 justify-end p-2 px-[11px]">
<Button size="sm" onClick={() => openTransferDialog()}>
{t('Transfer')}
</Button>
<Button size="sm" onClick={() => openDepositDialog()}>
{t('Deposit')}
</Button>
+39
View File
@@ -0,0 +1,39 @@
import {
AnnouncementBanner,
ExternalLink,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { useGlobalStore } from '../../stores';
export const Banner = () => {
const { update, shouldDisplayAnnouncementBanner } = useGlobalStore(
(store) => ({
update: store.update,
shouldDisplayAnnouncementBanner: store.shouldDisplayAnnouncementBanner,
})
);
// Return an empty div so that the grid layout in _app.page.ts
// renders correctly
if (!shouldDisplayAnnouncementBanner) {
return <div />;
}
return (
<AnnouncementBanner>
<div className="grid grid-cols-[auto_1fr] gap-4 font-alpha calt uppercase text-center text-lg text-white">
<button
onClick={() => update({ shouldDisplayAnnouncementBanner: false })}
>
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
</button>
<div>
<span className="pr-4">The Mainnet sims are live!</span>
<ExternalLink href="https://fairground.wtf/">
Come help stress test the network
</ExternalLink>
</div>
</div>
</AnnouncementBanner>
);
};
+1
View File
@@ -0,0 +1 @@
export * from './banner';
+1 -1
View File
@@ -15,7 +15,7 @@ export const Header = ({ title, children }: TradeMarketHeaderProps) => {
<div className="px-4 xl:px-0 pb-2 xl:pb-3">{title}</div>
<div
data-testid="header-summary"
className="flex flex-nowrap items-start xl:flex-1 w-full overflow-x-auto text-xs"
className="flex flex-nowrap items-end xl:flex-1 w-full overflow-x-auto text-xs"
>
{Children.map(children, (child, index) => {
if (!child) return null;
+2 -2
View File
@@ -8,7 +8,7 @@ import {
useLinks,
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/react-helpers';
import { useGlobalStore } from '../../stores/global';
import { useGlobalStore } from '../../stores';
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
import {
Drawer,
@@ -165,7 +165,7 @@ export const Navbar = ({ navbarTheme = 'inherit' }: NavbarProps) => {
</Link>
}
>
<LinkList className="hidden md:flex" navbarTheme={navbarTheme} />
<LinkList className="hidden md:flex md:px-2" navbarTheme={navbarTheme} />
<div className="flex items-center gap-2 ml-auto overflow-hidden">
<VegaWalletConnectButton />
<ThemeSwitcher className="hidden md:block" />
@@ -1 +0,0 @@
export { VegaTransaction } from './vega-transaction';
@@ -1,57 +0,0 @@
import { WithdrawalFeedback } from '@vegaprotocol/withdraws';
import { OrderFeedback } from '@vegaprotocol/orders';
import {
VegaDialog,
VegaTxStatus,
isWithdrawTransaction,
isOrderCancellationTransaction,
isOrderSubmissionTransaction,
isOrderAmendmentTransaction,
} from '@vegaprotocol/wallet';
import type { VegaStoredTxState } from '@vegaprotocol/wallet';
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
export const VegaTransaction = ({
transaction,
}: {
transaction: VegaStoredTxState;
}) => {
const createEthWithdrawalApproval = useEthWithdrawApprovalsStore(
(state) => state.create
);
if (isWithdrawTransaction(transaction.body)) {
if (
transaction.status === VegaTxStatus.Complete &&
transaction.withdrawal
) {
return (
<WithdrawalFeedback
transaction={transaction}
withdrawal={transaction.withdrawal}
availableTimestamp={null}
submitWithdraw={() => {
if (!transaction?.withdrawal) {
return;
}
createEthWithdrawalApproval(
transaction.withdrawal,
transaction.withdrawalApproval
);
}}
/>
);
}
} else if (
(isOrderCancellationTransaction(transaction.body) ||
isOrderSubmissionTransaction(transaction.body) ||
isOrderAmendmentTransaction(transaction.body)) &&
transaction.status === VegaTxStatus.Complete &&
transaction.order
) {
return (
<OrderFeedback transaction={transaction} order={transaction.order} />
);
}
return <VegaDialog transaction={transaction} />;
};
@@ -13,11 +13,13 @@ import {
DropdownMenuTrigger,
Icon,
Drawer,
DropdownMenuSeparator,
} from '@vegaprotocol/ui-toolkit';
import type { PubKey } from '@vegaprotocol/wallet';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import { WalletIcon } from '../icons/wallet';
import { useTransferDialog } from '@vegaprotocol/accounts';
const MobileWalletButton = ({
isConnected,
@@ -30,6 +32,7 @@ const MobileWalletButton = ({
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const openTransferDialog = useTransferDialog((store) => store.open);
const { VEGA_ENV } = useEnvironment();
const isYellow = VEGA_ENV === Networks.TESTNET;
const [drawerOpen, setDrawerOpen] = useState(false);
@@ -115,7 +118,16 @@ const MobileWalletButton = ({
/>
))}
</div>
<div className="m-4">
<div className="flex flex-col gap-2 m-4">
<Button
onClick={() => {
setDrawerOpen(false);
openTransferDialog(true);
}}
fill
>
{t('Transfer')}
</Button>
<Button onClick={mobileDisconnect} fill>
{t('Disconnect')}
</Button>
@@ -131,6 +143,7 @@ export const VegaWalletConnectButton = () => {
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const openTransferDialog = useTransferDialog((store) => store.open);
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
const isConnected = pubKey !== null;
@@ -171,6 +184,10 @@ export const VegaWalletConnectButton = () => {
<KeypairItem key={pk.publicKey} pk={pk} />
))}
</DropdownMenuRadioGroup>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => openTransferDialog(true)}>
{t('Transfer')}
</DropdownMenuItem>
<DropdownMenuItem data-testid="disconnect" onClick={disconnect}>
{t('Disconnect')}
</DropdownMenuItem>
@@ -3,8 +3,12 @@ import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { ETHERSCAN_TX, useEtherscanLink } from '@vegaprotocol/environment';
import { formatNumber, t, toBigNum } from '@vegaprotocol/react-helpers';
import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { Panel } from '@vegaprotocol/ui-toolkit';
import { CLOSE_AFTER } from '@vegaprotocol/ui-toolkit';
import { useToasts } from '@vegaprotocol/ui-toolkit';
import { ExternalLink, Intent, ProgressBar } from '@vegaprotocol/ui-toolkit';
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import compact from 'lodash/compact';
import type { EthStoredTxState } from '@vegaprotocol/web3';
import {
@@ -44,34 +48,35 @@ const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
if (isWithdraw) label = t('Withdraw');
if (isDeposit) label = t('Deposit');
assetInfo = (
<div className="mt-[5px]">
<span className="font-mono text-xs p-1 bg-gray-100 rounded">
{label}{' '}
{formatNumber(toBigNum(tx.args[1], asset.decimals), asset.decimals)}{' '}
{asset.symbol}
</span>
</div>
<strong>
{label}{' '}
{formatNumber(toBigNum(tx.args[1], asset.decimals), asset.decimals)}{' '}
{asset.symbol}
</strong>
);
}
}
return (
<>
{assetInfo}
{tx.status === EthTxStatus.Pending && (
<div className="mt-[10px]">
<span className="font-mono text-xs">
{t('Awaiting confirmations')}{' '}
{`(${tx.confirmations}/${tx.requiredConfirmations})`}
</span>
<ProgressBar
value={(tx.confirmations / tx.requiredConfirmations) * 100}
intent={Intent.Warning}
/>
</div>
)}
</>
);
if (assetInfo || tx.requiresConfirmation) {
return (
<Panel>
{assetInfo}
{tx.status === EthTxStatus.Pending && (
<>
<p className="mt-[2px]">
{t('Awaiting confirmations')}{' '}
{`(${tx.confirmations}/${tx.requiredConfirmations})`}
</p>
<ProgressBar
value={(tx.confirmations / tx.requiredConfirmations) * 100}
/>
</>
)}
</Panel>
);
}
return null;
};
type EthTxToastContentProps = {
@@ -80,26 +85,26 @@ type EthTxToastContentProps = {
const EthTxRequestedToastContent = ({ tx }: EthTxToastContentProps) => {
return (
<div>
<h3 className="font-bold">{t('Action required')}</h3>
<>
<ToastHeading>{t('Action required')}</ToastHeading>
<p>
{t(
'Please go to your wallet application and approve or reject the transaction.'
)}
</p>
<EthTransactionDetails tx={tx} />
</div>
</>
);
};
const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
return (
<div>
<h3 className="font-bold">{t('Awaiting confirmation')}</h3>
<>
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
<p>{t('Please wait for your transaction to be confirmed.')}</p>
<EtherscanLink tx={tx} />
<EthTransactionDetails tx={tx} />
</div>
</>
);
};
@@ -112,11 +117,11 @@ const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
errorMessage = tx.error.message;
}
return (
<div>
<h3 className="font-bold">{t('Error occurred')}</h3>
<p>{errorMessage}</p>
<>
<ToastHeading>{t('Error occurred')}</ToastHeading>
<p className="first-letter:uppercase">{errorMessage}</p>
<EthTransactionDetails tx={tx} />
</div>
</>
);
};
@@ -136,42 +141,63 @@ const EtherscanLink = ({ tx }: EthTxToastContentProps) => {
const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
return (
<div>
<h3 className="font-bold">{t('Transaction confirmed')}</h3>
<>
<ToastHeading>{t('Transaction confirmed')}</ToastHeading>
<p>{t('Your transaction has been confirmed.')}</p>
<EtherscanLink tx={tx} />
<EthTransactionDetails tx={tx} />
</div>
</>
);
};
const EthTxCompletedToastContent = ({ tx }: EthTxToastContentProps) => {
const isDeposit = isDepositTransaction(tx);
return (
<div>
<h3 className="font-bold">
<>
<ToastHeading>
{t('Processing')} {isDeposit && t('deposit')}
</h3>
</ToastHeading>
<p>
{t('Your transaction has been completed.')}{' '}
{isDeposit && t('Waiting for deposit confirmation.')}
</p>
<EtherscanLink tx={tx} />
<EthTransactionDetails tx={tx} />
</div>
</>
);
};
const isFinal = (tx: EthStoredTxState) =>
[EthTxStatus.Confirmed, EthTxStatus.Error].includes(tx.status);
export const useEthereumTransactionToasts = () => {
const ethTransactions = useEthTransactionStore((state) =>
state.transactions.filter((transaction) => transaction?.dialogOpen)
);
const dismissEthTransaction = useEthTransactionStore(
(state) => state.dismiss
const [setToast, removeToast] = useToasts((store) => [
store.setToast,
store.remove,
]);
const [dismissTx, deleteTx] = useEthTransactionStore((state) => [
state.dismiss,
state.delete,
]);
const onClose = useCallback(
(tx: EthStoredTxState) => () => {
const safeToDelete = isFinal(tx);
if (safeToDelete) {
deleteTx(tx.id);
} else {
dismissTx(tx.id);
}
removeToast(`eth-${tx.id}`);
},
[deleteTx, dismissTx, removeToast]
);
const fromEthTransaction = useCallback(
(tx: EthStoredTxState): Toast => {
let content: ToastContent = <TransactionContent {...tx} />;
const closeAfter = isFinal(tx) ? CLOSE_AFTER : undefined;
if (tx.status === EthTxStatus.Requested) {
content = <EthTxRequestedToastContent tx={tx} />;
}
@@ -191,15 +217,21 @@ export const useEthereumTransactionToasts = () => {
return {
id: `eth-${tx.id}`,
intent: intentMap[tx.status],
onClose: () => dismissEthTransaction(tx.id),
onClose: onClose(tx),
loader: [EthTxStatus.Pending, EthTxStatus.Complete].includes(tx.status),
content,
closeAfter,
};
},
[dismissEthTransaction]
[onClose]
);
return useMemo(() => {
return [...compact(ethTransactions).map(fromEthTransaction)];
}, [ethTransactions, fromEthTransaction]);
useEthTransactionStore.subscribe(
(state) => compact(state.transactions.filter((tx) => tx?.dialogOpen)),
(txs) => {
txs.forEach((tx) => {
setToast(fromEthTransaction(tx));
});
}
);
};
@@ -1,8 +1,12 @@
import { formatNumber, t, toBigNum } from '@vegaprotocol/react-helpers';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { Panel } from '@vegaprotocol/ui-toolkit';
import { CLOSE_AFTER } from '@vegaprotocol/ui-toolkit';
import { useToasts } from '@vegaprotocol/ui-toolkit';
import { Intent } from '@vegaprotocol/ui-toolkit';
import { ApprovalStatus, VerificationStatus } from '@vegaprotocol/withdraws';
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import compact from 'lodash/compact';
import type { EthWithdrawalApprovalState } from '@vegaprotocol/web3';
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
@@ -30,49 +34,72 @@ const EthWithdrawalApprovalToastContent = ({
if (tx.status === ApprovalStatus.Delayed) {
title = t('Delayed');
}
if (tx.status === ApprovalStatus.Ready) {
title = t('Approved');
}
const num = formatNumber(
toBigNum(tx.withdrawal.amount, tx.withdrawal.asset.decimals),
tx.withdrawal.asset.decimals
);
const details = (
<div className="mt-[5px]">
<span className="font-mono text-xs p-1 bg-gray-100 rounded">
<Panel>
<strong>
{t('Withdraw')} {num} {tx.withdrawal.asset.symbol}
</span>
</div>
</strong>
</Panel>
);
return (
<div>
{title.length > 0 && <h3 className="font-bold">{title}</h3>}
<>
{title.length > 0 && (
<ToastHeading className="font-bold">{title}</ToastHeading>
)}
<VerificationStatus state={tx} />
{details}
</div>
</>
);
};
const isFinal = (tx: EthWithdrawalApprovalState) =>
[ApprovalStatus.Ready, ApprovalStatus.Error].includes(tx.status);
export const useEthereumWithdrawApprovalsToasts = () => {
const { withdrawApprovals, dismissWithdrawApproval } =
useEthWithdrawApprovalsStore((state) => ({
withdrawApprovals: state.transactions.filter(
(transaction) => transaction?.dialogOpen
),
dismissWithdrawApproval: state.dismiss,
}));
const [setToast, remove] = useToasts((state) => [
state.setToast,
state.remove,
]);
const [dismissTx, deleteTx] = useEthWithdrawApprovalsStore((state) => [
state.dismiss,
state.delete,
]);
const fromWithdrawalApproval = useCallback(
(tx: EthWithdrawalApprovalState): Toast => ({
id: `withdrawal-${tx.id}`,
intent: intentMap[tx.status],
onClose: () => dismissWithdrawApproval(tx.id),
onClose: () => {
if ([ApprovalStatus.Error, ApprovalStatus.Ready].includes(tx.status)) {
deleteTx(tx.id);
} else {
dismissTx(tx.id);
}
remove(`withdrawal-${tx.id}`);
},
loader: tx.status === ApprovalStatus.Pending,
content: <EthWithdrawalApprovalToastContent tx={tx} />,
closeAfter: isFinal(tx) ? CLOSE_AFTER : undefined,
}),
[dismissWithdrawApproval]
[deleteTx, dismissTx, remove]
);
const toasts = useMemo(() => {
return [...compact(withdrawApprovals).map(fromWithdrawalApproval)];
}, [fromWithdrawalApproval, withdrawApprovals]);
return toasts;
useEthWithdrawApprovalsStore.subscribe(
(state) =>
compact(
state.transactions.filter((transaction) => transaction?.dialogOpen)
),
(txs) => {
txs.forEach((tx) => {
setToast(fromWithdrawalApproval(tx));
});
}
);
};
@@ -134,23 +134,7 @@ const submitOrder: VegaStoredTxState = {
type: OrderType.TYPE_MARKET,
price: '1234',
createdAt: new Date(),
market: {
id: 'market-1',
decimalPlaces: 2,
positionDecimalPlaces: 2,
tradableInstrument: {
instrument: {
code: 'M1',
name: 'M1',
product: {
settlementAsset: {
decimals: 2,
symbol: '$A',
},
},
},
},
},
marketId: 'market-1',
status: OrderStatus.STATUS_ACTIVE,
},
};
@@ -181,23 +165,7 @@ const editOrder: VegaStoredTxState = {
type: OrderType.TYPE_MARKET,
price: '1234',
createdAt: new Date(),
market: {
id: 'market-1',
decimalPlaces: 2,
positionDecimalPlaces: 2,
tradableInstrument: {
instrument: {
code: 'M1',
name: 'M1',
product: {
settlementAsset: {
decimals: 2,
symbol: '$A',
},
},
},
},
},
marketId: 'market-1',
status: OrderStatus.STATUS_ACTIVE,
},
};
@@ -292,7 +260,7 @@ describe('VegaTransactionDetails', () => {
const { queryByTestId } = render(
<VegaTransactionDetails tx={unsupportedTransaction} />
);
expect(queryByTestId('vega-tx-details')).toBeNull();
expect(queryByTestId('toast-panel')).toBeNull();
});
it.each([
{ tx: withdraw, details: 'Withdraw 12.34 $A' },
@@ -307,6 +275,6 @@ describe('VegaTransactionDetails', () => {
{ tx: batch, details: 'Batch market instruction' },
])('display details for transaction', ({ tx, details }) => {
const { queryByTestId } = render(<VegaTransactionDetails tx={tx} />);
expect(queryByTestId('vega-tx-details')?.textContent).toEqual(details);
expect(queryByTestId('toast-panel')?.textContent).toEqual(details);
});
});
@@ -1,22 +1,21 @@
import type { ReactNode } from 'react';
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import first from 'lodash/first';
import compact from 'lodash/compact';
import type {
BatchMarketInstructionSubmissionBody,
OrderAmendment,
OrderBusEventFieldsFragment,
OrderTxUpdateFieldsFragment,
OrderCancellationBody,
OrderSubmission,
VegaStoredTxState,
WithdrawalBusEventFieldsFragment,
} from '@vegaprotocol/wallet';
import { isBatchMarketInstructionsTransaction } from '@vegaprotocol/wallet';
import {
isTransferTransaction,
isBatchMarketInstructionsTransaction,
ClientErrors,
useReconnectVegaWallet,
WalletError,
} from '@vegaprotocol/wallet';
import {
isOrderAmendmentTransaction,
isOrderCancellationTransaction,
isOrderSubmissionTransaction,
@@ -25,6 +24,10 @@ import {
VegaTxStatus,
} from '@vegaprotocol/wallet';
import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { Panel } from '@vegaprotocol/ui-toolkit';
import { CLOSE_AFTER } from '@vegaprotocol/ui-toolkit';
import { useToasts } from '@vegaprotocol/ui-toolkit';
import { Button, ExternalLink, Intent } from '@vegaprotocol/ui-toolkit';
import {
addDecimalsFormatNumber,
@@ -32,14 +35,15 @@ import {
Size,
t,
toBigNum,
truncateByChars,
} from '@vegaprotocol/react-helpers';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
import { DApp, EXPLORER_TX, useLinks } from '@vegaprotocol/environment';
import { getRejectionReason, useOrderByIdQuery } from '@vegaprotocol/orders';
import { useMarketList } from '@vegaprotocol/market-list';
import first from 'lodash/first';
import type { Side } from '@vegaprotocol/types';
import { OrderStatus } from '@vegaprotocol/types';
import { OrderStatusMapping } from '@vegaprotocol/types';
const intentMap: { [s in VegaTxStatus]: Intent } = {
@@ -78,29 +82,17 @@ const isTransactionTypeSupported = (tx: VegaStoredTxState) => {
const cancelOrder = isOrderCancellationTransaction(tx.body);
const editOrder = isOrderAmendmentTransaction(tx.body);
const batchMarketInstructions = isBatchMarketInstructionsTransaction(tx.body);
const transfer = isTransferTransaction(tx.body);
return (
withdraw ||
submitOrder ||
cancelOrder ||
editOrder ||
batchMarketInstructions
batchMarketInstructions ||
transfer
);
};
const Details = ({
children,
title = '',
}: {
children: ReactNode;
title?: string;
}) => (
<div className="pt-[5px]" data-testid="vega-tx-details" title={title}>
<div className="font-mono text-xs p-2 bg-neutral-100 rounded dark:bg-neutral-700 dark:text-white">
{children}
</div>
</div>
);
type SizeAtPriceProps = {
side: Side;
size: string;
@@ -130,12 +122,10 @@ const SubmitOrderDetails = ({
order,
}: {
data: OrderSubmission;
order?: OrderBusEventFieldsFragment;
order?: OrderTxUpdateFieldsFragment;
}) => {
const { data: markets } = useMarketList();
const market = order
? order.market
: markets?.find((m) => m.id === data.marketId);
const market = markets?.find((m) => m.id === order?.marketId);
if (!market) return null;
const price = order ? order.price : data.price;
@@ -143,8 +133,8 @@ const SubmitOrderDetails = ({
const side = order ? order.side : data.side;
return (
<Details>
<h4 className="font-bold">
<Panel>
<h4>
{order
? t(
`Submit order - ${OrderStatusMapping[order.status].toLowerCase()}`
@@ -166,10 +156,7 @@ const SubmitOrderDetails = ({
price={price}
/>
</p>
{order && order.rejectionReason && (
<p className="italic">{getRejectionReason(order)}</p>
)}
</Details>
</Panel>
);
};
@@ -178,16 +165,17 @@ const EditOrderDetails = ({
order,
}: {
data: OrderAmendment;
order?: OrderBusEventFieldsFragment;
order?: OrderTxUpdateFieldsFragment;
}) => {
const { data: orderById } = useOrderByIdQuery({
variables: { orderId: data.orderId },
});
const { data: markets } = useMarketList();
const originalOrder = orderById?.orderByID;
const originalOrder = order || orderById?.orderByID;
const marketId = order?.marketId || orderById?.orderByID.market.id;
if (!originalOrder) return null;
const market = markets?.find((m) => m.id === originalOrder.market.id);
const market = markets?.find((m) => m.id === marketId);
if (!market) return null;
const original = (
@@ -219,8 +207,8 @@ const EditOrderDetails = ({
);
return (
<Details title={data.orderId}>
<h4 className="font-bold">
<Panel title={data.orderId}>
<h4>
{order
? t(`Edit order - ${OrderStatusMapping[order.status].toLowerCase()}`)
: t('Edit order')}
@@ -230,10 +218,7 @@ const EditOrderDetails = ({
<s>{original}</s>
</p>
<p>{edited}</p>
{order && order.rejectionReason && (
<p className="italic">{getRejectionReason(order)}</p>
)}
</Details>
</Panel>
);
};
@@ -242,7 +227,7 @@ const CancelOrderDetails = ({
order,
}: {
orderId: string;
order?: OrderBusEventFieldsFragment;
order?: OrderTxUpdateFieldsFragment;
}) => {
const { data: orderById } = useOrderByIdQuery({
variables: { orderId },
@@ -268,8 +253,8 @@ const CancelOrderDetails = ({
/>
);
return (
<Details title={orderId}>
<h4 className="font-bold">
<Panel title={orderId}>
<h4>
{order
? t(
`Cancel order - ${OrderStatusMapping[order.status].toLowerCase()}`
@@ -280,10 +265,7 @@ const CancelOrderDetails = ({
<p>
<s>{original}</s>
</p>
{order && order.rejectionReason && (
<p className="italic">{getRejectionReason(order)}</p>
)}
</Details>
</Panel>
);
};
@@ -302,9 +284,11 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
asset.decimals
);
return (
<Details>
{t('Withdraw')} {num} {asset.symbol}
</Details>
<Panel>
<strong>
{t('Withdraw')} {num} {asset.symbol}
</strong>
</Panel>
);
}
}
@@ -321,7 +305,7 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
tx.body.orderCancellation.marketId === undefined &&
tx.body.orderCancellation.orderId === undefined
) {
return <Details>{t('Cancel all orders')}</Details>;
return <Panel>{t('Cancel all orders')}</Panel>;
}
// CANCEL
@@ -344,11 +328,15 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
m.id === (tx.body as OrderCancellationBody).orderCancellation.marketId
)?.tradableInstrument.instrument.code;
return (
<Details>
{marketName
? `${t('Cancel all orders for')} ${marketName}`
: t('Cancel all orders')}
</Details>
<Panel>
{marketName ? (
<>
{t('Cancel all orders for')} <strong>{marketName}</strong>
</>
) : (
t('Cancel all orders')
)}
</Panel>
);
}
}
@@ -370,15 +358,36 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
const market = marketId && markets?.find((m) => m.id === marketId);
if (market) {
return (
<Details>
{t('Close position for')} {market.tradableInstrument.instrument.code}
</Details>
<Panel>
{t('Close position for')}{' '}
<strong>{market.tradableInstrument.instrument.code}</strong>
</Panel>
);
}
}
if (isBatchMarketInstructionsTransaction(tx.body)) {
return <Details>{t('Batch market instruction')}</Details>;
return <Panel>{t('Batch market instruction')}</Panel>;
}
if (isTransferTransaction(tx.body)) {
const { amount, to, asset } = tx.body.transfer;
const transferAsset = assets?.find((a) => a.id === asset);
// only render if we have an asset to avoid unformatted amounts showing
if (transferAsset) {
const value = addDecimalsFormatNumber(amount, transferAsset.decimals);
return (
<Panel>
<h4>{t('Transfer')}</h4>
<p>
{t('To')} {truncateByChars(to)}
</p>
<p>
{value} {transferAsset.symbol}
</p>
</Panel>
);
}
}
return null;
@@ -387,22 +396,22 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
type VegaTxToastContentProps = { tx: VegaStoredTxState };
const VegaTxRequestedToastContent = ({ tx }: VegaTxToastContentProps) => (
<div>
<h3 className="font-bold">{t('Action required')}</h3>
<>
<ToastHeading>{t('Action required')}</ToastHeading>
<p>
{t(
'Please go to your Vega wallet application and approve or reject the transaction.'
)}
</p>
<VegaTransactionDetails tx={tx} />
</div>
</>
);
const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
const explorerLink = useLinks(DApp.Explorer);
return (
<div>
<h3 className="font-bold">{t('Awaiting confirmation')}</h3>
<>
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
<p>{t('Please wait for your transaction to be confirmed')}</p>
{tx.txHash && (
<p className="break-all">
@@ -415,7 +424,7 @@ const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
</p>
)}
<VegaTransactionDetails tx={tx} />
</div>
</>
);
};
@@ -426,9 +435,10 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
})
);
const explorerLink = useLinks(DApp.Explorer);
if (isWithdrawTransaction(tx.body)) {
const completeWithdrawalButton = tx.withdrawal && (
<div className="mt-[10px]">
<p className="mt-1">
<Button
data-testid="toast-complete-withdrawal"
size="xs"
@@ -441,11 +451,11 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
>
{t('Complete withdrawal')}
</Button>
</div>
</p>
);
return (
<div>
<h3 className="font-bold">{t('Funds unlocked')}</h3>
<>
<ToastHeading>{t('Funds unlocked')}</ToastHeading>
<p>{t('Your funds have been unlocked for withdrawal')}</p>
{tx.txHash && (
<p className="break-all">
@@ -459,13 +469,68 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
)}
<VegaTransactionDetails tx={tx} />
{completeWithdrawalButton}
</>
);
}
if (tx.order && tx.order.rejectionReason) {
return (
<>
<ToastHeading>{t('Order rejected')}</ToastHeading>
<p>
{t(
'Your order has been rejected because: %s',
getRejectionReason(tx.order) || ''
)}
</p>
{tx.txHash && (
<p className="break-all">
<ExternalLink
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
rel="noreferrer"
>
{t('View in block explorer')}
</ExternalLink>
</p>
)}
<VegaTransactionDetails tx={tx} />
</>
);
}
if (isOrderSubmissionTransaction(tx.body) && tx.order?.rejectionReason) {
return (
<div>
<h3 className="font-bold">{t('Order rejected')}</h3>
<p>{t('Your order was rejected.')}</p>
{tx.txHash && (
<p className="break-all">
<ExternalLink
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
rel="noreferrer"
>
{t('View in block explorer')}
</ExternalLink>
</p>
)}
<VegaTransactionDetails tx={tx} />
</div>
);
}
if (isTransferTransaction(tx.body)) {
return (
<div>
<h3 className="font-bold">{t('Transfer complete')}</h3>
<p>{t('Your transaction has been confirmed ')}</p>
<VegaTransactionDetails tx={tx} />
</div>
);
}
return (
<div>
<h3 className="font-bold">{t('Confirmed')}</h3>
<>
<ToastHeading>{t('Confirmed')}</ToastHeading>
<p>{t('Your transaction has been confirmed ')}</p>
{tx.txHash && (
<p className="break-all">
@@ -478,7 +543,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
</p>
)}
<VegaTransactionDetails tx={tx} />
</div>
</>
);
};
@@ -501,7 +566,10 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
walletNoConnectionCodes.includes(tx.error.code);
if (orderRejection) {
label = t('Order rejected');
errorMessage = orderRejection;
errorMessage = t(
'Your order has been rejected because: %s',
orderRejection
);
}
if (walletError) {
label = t('Wallet disconnected');
@@ -509,60 +577,87 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
}
return (
<div>
<h3 className="font-bold">{label}</h3>
<p>{errorMessage}</p>
<>
<ToastHeading>{label}</ToastHeading>
<p className="first-letter:uppercase">{errorMessage}</p>
{walletError && (
<Button size="xs" onClick={reconnectVegaWallet}>
{t('Connect vega wallet')}
</Button>
)}
<VegaTransactionDetails tx={tx} />
</div>
</>
);
};
const isFinal = (tx: VegaStoredTxState) =>
[VegaTxStatus.Error, VegaTxStatus.Complete].includes(tx.status);
export const useVegaTransactionToasts = () => {
const vegaTransactions = useVegaTransactionStore((state) =>
state.transactions.filter((transaction) => transaction?.dialogOpen)
);
const dismissVegaTransaction = useVegaTransactionStore(
(state) => state.dismiss
);
const [setToast, removeToast] = useToasts((store) => [
store.setToast,
store.remove,
]);
const fromVegaTransaction = useCallback(
(tx: VegaStoredTxState): Toast => {
let content: ToastContent;
if (tx.status === VegaTxStatus.Requested) {
content = <VegaTxRequestedToastContent tx={tx} />;
const [dismissTx, deleteTx] = useVegaTransactionStore((state) => [
state.dismiss,
state.delete,
]);
const onClose = useCallback(
(tx: VegaStoredTxState) => () => {
const safeToDelete = isFinal(tx);
if (safeToDelete) {
deleteTx(tx.id);
} else {
dismissTx(tx.id);
}
if (tx.status === VegaTxStatus.Pending) {
content = <VegaTxPendingToastContentProps tx={tx} />;
}
if (tx.status === VegaTxStatus.Complete) {
content = <VegaTxCompleteToastsContent tx={tx} />;
}
if (tx.status === VegaTxStatus.Error) {
content = <VegaTxErrorToastContent tx={tx} />;
}
return {
id: `vega-${tx.id}`,
intent: intentMap[tx.status],
onClose: () => dismissVegaTransaction(tx.id),
loader: tx.status === VegaTxStatus.Pending,
content,
};
removeToast(`vega-${tx.id}`);
},
[dismissVegaTransaction]
[deleteTx, dismissTx, removeToast]
);
const toasts = useMemo(() => {
return [
...compact(vegaTransactions)
.filter((tx) => isTransactionTypeSupported(tx))
.map(fromVegaTransaction),
];
}, [fromVegaTransaction, vegaTransactions]);
const fromVegaTransaction = (tx: VegaStoredTxState): Toast => {
let content: ToastContent;
const closeAfter = isFinal(tx) ? CLOSE_AFTER : undefined;
if (tx.status === VegaTxStatus.Requested) {
content = <VegaTxRequestedToastContent tx={tx} />;
}
if (tx.status === VegaTxStatus.Pending) {
content = <VegaTxPendingToastContentProps tx={tx} />;
}
if (tx.status === VegaTxStatus.Complete) {
content = <VegaTxCompleteToastsContent tx={tx} />;
}
if (tx.status === VegaTxStatus.Error) {
content = <VegaTxErrorToastContent tx={tx} />;
}
return toasts;
// Transaction can be successful but the order can be rejected by the network
const intent =
tx.order && [OrderStatus.STATUS_REJECTED].includes(tx.order.status)
? Intent.Danger
: intentMap[tx.status];
return {
id: `vega-${tx.id}`,
intent,
onClose: onClose(tx),
loader: tx.status === VegaTxStatus.Pending,
content,
closeAfter,
};
};
useVegaTransactionStore.subscribe(
(state) =>
compact(
state.transactions.filter(
(tx) => tx?.dialogOpen && isTransactionTypeSupported(tx)
)
),
(txs) => {
txs.forEach((tx) => setToast(fromVegaTransaction(tx)));
}
);
};
+13 -5
View File
@@ -33,6 +33,8 @@ import ToastsManager from './toasts-manager';
import { HashRouter, useLocation, useSearchParams } from 'react-router-dom';
import { Connectors } from '../lib/vega-connectors';
import { ViewingBanner } from '../components/viewing-banner';
import { Banner } from '../components/banner';
import classNames from 'classnames';
const DEFAULT_TITLE = t('Welcome to Vega trading!');
@@ -70,6 +72,11 @@ function AppBody({ Component }: AppProps) {
const location = useLocation();
const { VEGA_ENV } = useEnvironment();
const gridClasses = classNames(
'h-full relative z-0 grid',
'grid-rows-[repeat(3,min-content),1fr,min-content]'
);
return (
<div className="h-full dark:bg-black dark:text-white">
<Head>
@@ -80,20 +87,21 @@ function AppBody({ Component }: AppProps) {
<VegaWalletProvider>
<AppLoader>
<Web3Provider>
<div className="h-full relative z-0 grid grid-rows-[min-content,min-content,1fr,min-content]">
<div className={gridClasses}>
<Navbar
navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'}
/>
<Banner />
<ViewingBanner />
<main data-testid={location.pathname}>
<Component />
</main>
<Footer />
<DialogsContainer />
<ToastsManager />
<TransactionsHandler />
<MaybeConnectEagerly />
</div>
<DialogsContainer />
<ToastsManager />
<TransactionsHandler />
<MaybeConnectEagerly />
</Web3Provider>
</AppLoader>
</VegaWalletProvider>
+1 -1
View File
@@ -22,7 +22,7 @@ export default function Document() {
<script src="/assets/env-config.js" type="text/javascript" />
) : null}
</Head>
<body>
<body className="font-alpha liga-0-calt-0">
<Main />
<NextScript />
</body>
+2
View File
@@ -8,6 +8,7 @@ import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { DepositDialog } from '@vegaprotocol/deposits';
import { Web3ConnectUncontrolledDialog } from '@vegaprotocol/web3';
import { WelcomeDialog } from '../components/welcome-dialog';
import { TransferDialog } from '@vegaprotocol/accounts';
const DialogsContainer = () => {
const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore();
@@ -25,6 +26,7 @@ const DialogsContainer = () => {
<DepositDialog />
<Web3ConnectUncontrolledDialog />
<CreateWithdrawalDialog />
<TransferDialog />
</>
);
};
+6 -25
View File
@@ -1,35 +1,16 @@
import { ToastsContainer } from '@vegaprotocol/ui-toolkit';
import { useMemo } from 'react';
import sortBy from 'lodash/sortBy';
import { ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
import { useUpdateNetworkParametersToasts } from '@vegaprotocol/governance';
import { useVegaTransactionToasts } from '../lib/hooks/use-vega-transaction-toasts';
import { useEthereumTransactionToasts } from '../lib/hooks/use-ethereum-transaction-toasts';
import { useEthereumWithdrawApprovalsToasts } from '../lib/hooks/use-ethereum-withdraw-approval-toasts';
export const ToastsManager = () => {
const updateNetworkParametersToasts = useUpdateNetworkParametersToasts();
const vegaTransactionToasts = useVegaTransactionToasts();
const ethTransactionToasts = useEthereumTransactionToasts();
const withdrawApprovalToasts = useEthereumWithdrawApprovalsToasts();
const toasts = useMemo(() => {
return sortBy(
[
...vegaTransactionToasts,
...ethTransactionToasts,
...withdrawApprovalToasts,
...updateNetworkParametersToasts,
],
['createdBy']
);
}, [
vegaTransactionToasts,
ethTransactionToasts,
withdrawApprovalToasts,
updateNetworkParametersToasts,
]);
useUpdateNetworkParametersToasts();
useVegaTransactionToasts();
useEthereumTransactionToasts();
useEthereumWithdrawApprovalsToasts();
const toasts = useToasts((store) => store.toasts);
return <ToastsContainer order="desc" toasts={toasts} />;
};
+2
View File
@@ -7,6 +7,7 @@ interface GlobalStore {
marketId: string | null;
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
shouldDisplayWelcomeDialog: boolean;
shouldDisplayAnnouncementBanner: boolean;
}
interface PageTitleStore {
@@ -18,6 +19,7 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
networkSwitcherDialog: false,
marketId: LocalStorage.getItem('marketId') || null,
shouldDisplayWelcomeDialog: false,
shouldDisplayAnnouncementBanner: true,
update: (newState) => {
set(
produce((state: GlobalStore) => {
+1
View File
@@ -7,3 +7,4 @@ export * from './breakdown-table';
export * from './use-account-balance';
export * from './get-settlement-account';
export * from './use-market-account-balance';
export * from './transfer-dialog';
@@ -0,0 +1,68 @@
import * as Schema from '@vegaprotocol/types';
import {
addDecimal,
NetworkParams,
t,
truncateByChars,
useDataProvider,
useNetworkParam,
} from '@vegaprotocol/react-helpers';
import type { Transfer } from '@vegaprotocol/wallet';
import { useVegaTransactionStore, useVegaWallet } from '@vegaprotocol/wallet';
import { useCallback, useMemo } from 'react';
import { accountsDataProvider } from './accounts-data-provider';
import { TransferForm } from './transfer-form';
import { useTransferDialog } from './transfer-dialog';
import { Lozenge } from '@vegaprotocol/ui-toolkit';
export const TransferContainer = () => {
const { pubKey, pubKeys } = useVegaWallet();
const open = useTransferDialog((store) => store.open);
const { param } = useNetworkParam(NetworkParams.transfer_fee_factor);
const { data } = useDataProvider({
dataProvider: accountsDataProvider,
variables: { partyId: pubKey },
skip: !pubKey,
});
const create = useVegaTransactionStore((store) => store.create);
const transfer = useCallback(
(transfer: Transfer) => {
create({ transfer });
open(false);
},
[create, open]
);
const assets = useMemo(() => {
if (!data) return [];
return data
.filter(
(account) => account.type === Schema.AccountType.ACCOUNT_TYPE_GENERAL
)
.map((account) => ({
id: account.asset.id,
symbol: account.asset.symbol,
name: account.asset.name,
decimals: account.asset.decimals,
balance: addDecimal(account.balance, account.asset.decimals),
}));
}, [data]);
return (
<>
<p className="text-sm mb-4">
{t('Transfer funds to another Vega key from')}{' '}
<Lozenge className="font-mono">{truncateByChars(pubKey || '')}</Lozenge>{' '}
{t('If you are at all unsure, stop and seek advice.')}
</p>
<TransferForm
pubKey={pubKey}
pubKeys={pubKeys ? pubKeys?.map((pk) => pk.publicKey) : null}
assets={assets}
feeFactor={param}
submitTransfer={transfer}
/>
</>
);
};
+28
View File
@@ -0,0 +1,28 @@
import { t } from '@vegaprotocol/react-helpers';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import { create } from 'zustand';
import { TransferContainer } from './transfer-container';
interface State {
isOpen: boolean;
}
interface Actions {
open: (open?: boolean) => void;
}
export const useTransferDialog = create<State & Actions>((set) => ({
isOpen: false,
open: (open = true) => {
set(() => ({ isOpen: open }));
},
}));
export const TransferDialog = () => {
const { isOpen, open } = useTransferDialog();
return (
<Dialog title={t('Transfer')} open={isOpen} onChange={open} size="small">
<TransferContainer />
</Dialog>
);
};
@@ -0,0 +1,177 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import BigNumber from 'bignumber.js';
import { AddressField, TransferFee, TransferForm } from './transfer-form';
import { AccountType } from '@vegaprotocol/types';
import { formatNumber, removeDecimal } from '@vegaprotocol/react-helpers';
describe('TransferForm', () => {
const submit = () => fireEvent.submit(screen.getByTestId('transfer-form'));
const amount = '100';
const pubKey =
'70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680';
const asset = {
id: 'asset-0',
symbol: 'ASSET 0',
name: 'Asset 0',
decimals: 2,
balance: '1000',
};
const props = {
pubKey,
pubKeys: [
pubKey,
'a4b6e3de5d7ef4e31ae1b090be49d1a2ef7bcefff60cccf7658a0d4922651cce',
],
assets: [asset],
feeFactor: '0.001',
submitTransfer: jest.fn(),
};
it('validates fields and submits', async () => {
render(<TransferForm {...props} />);
// check current pubkey not shown
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
expect(keySelect.children).toHaveLength(2);
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
'',
props.pubKeys[1],
]);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
// Select a pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: props.pubKeys[1] },
});
// Select asset
fireEvent.change(
// Bypass RichSelect and target hidden native select
// eslint-disable-next-line
document.querySelector('select[name="asset"]')!,
{ target: { value: asset.id } }
);
// assert rich select as updated
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
asset.name
);
expect(screen.getByTestId('asset-balance')).toHaveTextContent(
formatNumber(asset.balance, asset.decimals)
);
// Test amount validation
fireEvent.change(screen.getByLabelText('Amount'), {
target: { value: '0.00000001' },
});
expect(
await screen.findByText('Value is below minimum')
).toBeInTheDocument();
fireEvent.change(screen.getByLabelText('Amount'), {
target: { value: '9999999' },
});
expect(
await screen.findByText(/cannot transfer more/i)
).toBeInTheDocument();
// set valid amount
fireEvent.change(screen.getByLabelText('Amount'), {
target: { value: amount },
});
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(
new BigNumber(props.feeFactor).times(amount).toFixed()
);
submit();
await waitFor(() => {
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
expect(props.submitTransfer).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKeys[1],
asset: asset.id,
amount: removeDecimal(amount, asset.decimals),
oneOff: {},
});
});
});
it('validates a manually entered address', async () => {
render(<TransferForm {...props} />);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
const toggle = screen.getByText('Enter manually');
fireEvent.click(toggle);
// has switched to input
expect(toggle).toHaveTextContent('Select from wallet');
expect(screen.getByLabelText('Vega key')).toHaveAttribute('type', 'text');
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: 'invalid-address' },
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Invalid Vega key');
});
// same pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: pubKey },
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Vega key is the same');
});
});
});
describe('AddressField', () => {
const props = {
pubKeys: ['pubkey-1', 'pubkey-2'],
select: <div>select</div>,
input: <div>input</div>,
onChange: jest.fn(),
};
it('toggles content and calls onChange', async () => {
const mockOnChange = jest.fn();
render(<AddressField {...props} onChange={mockOnChange} />);
// select should be shown as multiple pubkeys provided
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
fireEvent.click(screen.getByText('Enter manually'));
expect(screen.queryByText('select')).not.toBeInTheDocument();
expect(screen.getByText('input')).toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByText('Select from wallet'));
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(2);
});
it('Does not provide select option if there is only a single key', () => {
render(<AddressField {...props} pubKeys={['single-pubKey']} />);
expect(screen.getByText('input')).toBeInTheDocument();
expect(screen.queryByText('Select from wallet')).not.toBeInTheDocument();
});
});
describe('TransferFee', () => {
const props = {
amount: '200',
feeFactor: '0.001',
};
it('calculates and renders the transfer fee', () => {
render(<TransferFee {...props} />);
const expected = new BigNumber(props.amount)
.times(props.feeFactor)
.toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expected);
});
});
+296
View File
@@ -0,0 +1,296 @@
import {
t,
minSafe,
maxSafe,
required,
vegaPublicKey,
addDecimal,
formatNumber,
} from '@vegaprotocol/react-helpers';
import {
Button,
FormGroup,
Input,
InputError,
Option,
RichSelect,
Select,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import type { Transfer } from '@vegaprotocol/wallet';
import { normalizeTransfer } from '@vegaprotocol/wallet';
import BigNumber from 'bignumber.js';
import type { ReactNode } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
interface FormFields {
toAddress: string;
asset: string;
amount: string;
}
interface TransferFormProps {
pubKey: string | null;
pubKeys: string[] | null;
assets: Array<{
id: string;
symbol: string;
name: string;
decimals: number;
balance: string;
}>;
feeFactor: string | null;
submitTransfer: (transfer: Transfer) => void;
}
export const TransferForm = ({
pubKey,
pubKeys,
assets,
feeFactor,
submitTransfer,
}: TransferFormProps) => {
const {
control,
register,
watch,
handleSubmit,
setValue,
formState: { errors },
} = useForm<FormFields>();
const amount = watch('amount');
const assetId = watch('asset');
const asset = useMemo(() => {
return assets.find((a) => a.id === assetId);
}, [assets, assetId]);
const onSubmit = useCallback(
(fields: FormFields) => {
if (!asset) {
throw new Error('Submitted transfer with no asset selected');
}
const transfer = normalizeTransfer(fields.toAddress, fields.amount, {
id: asset.id,
decimals: asset.decimals,
});
submitTransfer(transfer);
},
[asset, submitTransfer]
);
const min = useMemo(() => {
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
const minViableAmount = asset
? new BigNumber(addDecimal('1', asset.decimals))
: new BigNumber(0);
return minViableAmount;
}, [asset]);
const max = useMemo(() => {
const maxAmount = asset ? new BigNumber(asset.balance) : new BigNumber(0);
return maxAmount;
}, [asset]);
return (
<form
onSubmit={handleSubmit(onSubmit)}
className="text-sm"
data-testid="transfer-form"
>
<FormGroup label="Vega key" labelFor="to-address">
<AddressField
pubKeys={pubKeys}
onChange={() => setValue('toAddress', '')}
select={
<Select {...register('toAddress')} id="to-address" defaultValue="">
<option value="" disabled={true}>
{t('Please select')}
</option>
{pubKeys?.length &&
pubKeys
.filter((pk) => pk !== pubKey) // remove currently selected pubkey
.map((pk) => (
<option key={pk} value={pk}>
{pk}
</option>
))}
</Select>
}
input={
<Input
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true} // focus input immediately after is shown
id="to-address"
type="text"
{...register('toAddress', {
validate: {
required,
vegaPublicKey,
sameKey: (value) => {
if (value === pubKey) {
return t('Vega key is the same as current key');
}
return true;
},
},
})}
/>
}
/>
{errors.toAddress?.message && (
<InputError forInput="to-address">
{errors.toAddress.message}
</InputError>
)}
</FormGroup>
<FormGroup label="Asset" labelFor="asset">
<Controller
control={control}
name="asset"
rules={{
validate: {
required,
},
}}
render={({ field }) => (
<RichSelect
data-testid="select-asset"
id={field.name}
name={field.name}
onValueChange={(value) => {
field.onChange(value);
}}
placeholder={t('Please select')}
value={field.value}
>
{assets.map((a) => (
<Option key={a.id} value={a.id}>
<div className="text-left" data-testid={`asset-${a.id}`}>
<div>{a.name}</div>
<div className="text-xs">
<span className="font-mono" data-testid="asset-balance">
{formatNumber(a.balance, a.decimals)}
</span>{' '}
<span>{a.symbol}</span>
</div>
</div>
</Option>
))}
</RichSelect>
)}
/>
{errors.asset?.message && (
<InputError forInput="asset">{errors.asset.message}</InputError>
)}
</FormGroup>
<FormGroup label="Amount" labelFor="amount">
<Input
id="amount"
autoComplete="off"
appendElement={
asset && <span className="text-xs">{asset.symbol}</span>
}
{...register('amount', {
validate: {
required,
minSafe: (value) => minSafe(new BigNumber(min))(value),
maxSafe: (v) => {
const value = new BigNumber(v);
if (value.isGreaterThan(max)) {
return t(
'You cannot transfer more than your available collateral'
);
}
return maxSafe(max)(v);
},
},
})}
/>
{errors.amount?.message && (
<InputError forInput="amount">{errors.amount.message}</InputError>
)}
</FormGroup>
<TransferFee amount={amount} feeFactor={feeFactor} />
<Button type="submit" variant="primary" fill={true}>
{t('Confirm transfer')}
</Button>
</form>
);
};
export const TransferFee = ({
amount,
feeFactor,
}: {
amount: string;
feeFactor: string | null;
}) => {
if (!feeFactor || !amount) return null;
// using toFixed without an argument will always return a
// number in normal notation without rounding, formatting functions
// arent working in a way which won't round the decimal places
const value = new BigNumber(amount).times(feeFactor).toFixed();
return (
<div className="mb-4 flex justify-between items-center gap-4 flex-wrap">
<div>
<Tooltip
description={t(
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to ${feeFactor}`
)}
>
<div>{t('Transfer fee')}</div>
</Tooltip>
</div>
<div
data-testid="transfer-fee"
className="text-neutral-500 dark:text-neutral-300"
>
{value}
</div>
</div>
);
};
interface AddressInputProps {
pubKeys: string[] | null;
select: ReactNode;
input: ReactNode;
onChange: () => void;
}
export const AddressField = ({
pubKeys,
select,
input,
onChange,
}: AddressInputProps) => {
const [isInput, setIsInput] = useState(() => {
if (pubKeys && pubKeys.length <= 1) {
return true;
}
return false;
});
return (
<>
{isInput ? input : select}
{pubKeys && pubKeys.length > 1 && (
<button
type="button"
onClick={() => {
setIsInput((curr) => !curr);
onChange();
}}
className="ml-auto text-sm absolute top-0 right-0 underline"
>
{isInput ? t('Select from wallet') : t('Enter manually')}
</button>
)}
</>
);
};
+5 -1
View File
@@ -1,5 +1,4 @@
import 'pennant/dist/style.css';
import {
Chart,
ChartType,
@@ -168,6 +167,11 @@ export const CandlesChartContainer = ({
chartType: chartType,
overlays: overlays,
studies: studies,
notEnoughDataText: (
<span className="text-xs text-center text-neutral-800 dark:text-neutral-200">
{t('No data')}
</span>
),
}}
interval={interval}
theme={theme}
@@ -20,14 +20,19 @@ export const AssetProposalNotification = ({
const proposalLink = tokenLink(
TOKEN_PROPOSAL.replace(':id', proposal.id || '')
);
const message = (
<>
{t('Changes have been proposed for this asset.')}{' '}
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
</>
);
return (
<Notification
intent={Intent.Warning}
message={t('Changes have been proposed for this asset')}
message={message}
testId="asset-proposal-notification"
>
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
</Notification>
className="mb-2"
/>
);
}
@@ -20,14 +20,23 @@ export const MarketProposalNotification = ({
const proposalLink = tokenLink(
TOKEN_PROPOSAL.replace(':id', proposal.id || '')
);
const message = (
<div className="flex flex-col text-sm">
{t('Changes have been proposed for this market.')}{' '}
<ExternalLink href={proposalLink} className="w-fit">
{t('View proposal')}
</ExternalLink>
</div>
);
return (
<Notification
intent={Intent.Warning}
message={t('Changes have been proposed for this market')}
testId="market-proposal-notification"
>
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
</Notification>
<div className="border-l border-default pl-1 pr-1 pb-1 min-w-min whitespace-nowrap">
<Notification
intent={Intent.Warning}
message={message}
testId="market-proposal-notification"
className="px-2 py-1"
/>
</div>
);
}
@@ -4,6 +4,7 @@ import type { UpdateNetworkParameter } from '@vegaprotocol/types';
import { ProposalStateMapping } from '@vegaprotocol/types';
import { ProposalState } from '@vegaprotocol/types';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { useToasts } from '@vegaprotocol/ui-toolkit';
import { ExternalLink, Intent } from '@vegaprotocol/ui-toolkit';
import compact from 'lodash/compact';
@@ -28,7 +29,7 @@ const UpdateNetworkParameterToastContent = ({
const enactment = Date.parse(proposal.terms.enactmentDatetime);
return (
<div>
<h3 className="font-bold">{title}</h3>
<ToastHeading>{title}</ToastHeading>
<p className="italic">
'
{t(
@@ -52,9 +53,8 @@ const UpdateNetworkParameterToastContent = ({
);
};
export const useUpdateNetworkParametersToasts = (): Toast[] => {
const { proposalToasts, setToast, remove } = useToasts((store) => ({
proposalToasts: store.toasts,
export const useUpdateNetworkParametersToasts = () => {
const { setToast, remove } = useToasts((store) => ({
setToast: store.setToast,
remove: store.remove,
}));
@@ -66,7 +66,9 @@ export const useUpdateNetworkParametersToasts = (): Toast[] => {
id: `update-network-param-proposal-${proposal.id}`,
intent: Intent.Warning,
content: <UpdateNetworkParameterToastContent proposal={proposal} />,
onClose: () => remove(id),
onClose: () => {
remove(id);
},
closeAfter: CLOSE_AFTER,
};
},
@@ -96,6 +98,4 @@ export const useUpdateNetworkParametersToasts = (): Toast[] => {
}
},
});
return proposalToasts;
};
@@ -1,6 +1,6 @@
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react-hooks';
import { ProposalState } from '@vegaprotocol/types';
import type { ReactNode } from 'react';
import { useUpdateNetworkParametersToasts } from './use-update-network-paramaters-toasts';
@@ -9,8 +9,8 @@ import type {
OnUpdateNetworkParametersSubscription,
} from './__generated__/Proposal';
import { OnUpdateNetworkParametersDocument } from './__generated__/Proposal';
import waitForNextTick from 'flush-promises';
import { useToasts } from '@vegaprotocol/ui-toolkit';
import { waitFor } from '@testing-library/react';
const render = (mocks?: MockedResponse[]) => {
const wrapper = ({ children }: { children: ReactNode }) => (
@@ -92,11 +92,10 @@ const mockedEvent: MockedResponse<OnUpdateNetworkParametersSubscription> = {
},
};
const INITIAL = useToasts.getState();
const clear = () => {
const { result: clearer } = renderHook(() =>
useToasts((store) => store.removeAll)
);
act(() => clearer.current());
useToasts.setState(INITIAL);
};
describe('useUpdateNetworkParametersToasts', () => {
@@ -104,29 +103,23 @@ describe('useUpdateNetworkParametersToasts', () => {
afterAll(clear);
it('returns toast for update network parameters bus event', async () => {
const { waitForNextUpdate, result } = render([mockedEvent]);
await act(async () => {
waitForNextUpdate();
await waitForNextTick();
render([mockedEvent]);
await waitFor(() => {
expect(useToasts.getState().count).toBe(1);
});
expect(result.current.length).toBe(1);
});
it('does not return toast for empty event', async () => {
const { waitForNextUpdate, result } = render([mockedEmptyEvent]);
await act(async () => {
waitForNextUpdate();
await waitForNextTick();
render([mockedEmptyEvent]);
await waitFor(() => {
expect(useToasts.getState().count).toBe(0);
});
expect(result.current.length).toBe(0);
});
it('does not return toast for wrong event', async () => {
const { waitForNextUpdate, result } = render([mockedWrongEvent]);
await act(async () => {
waitForNextUpdate();
await waitForNextTick();
render([mockedWrongEvent]);
await waitFor(() => {
expect(useToasts.getState().count).toBe(0);
});
expect(result.current.length).toBe(0);
});
});
@@ -6,6 +6,7 @@ import {
addDecimal,
getNumberFormat,
useThemeSwitcher,
t,
} from '@vegaprotocol/react-helpers';
import { marketDepthProvider } from './market-depth-provider';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -207,6 +208,11 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
theme={theme}
volumeFormat={volumeFormat}
priceFormat={priceFormat}
notEnoughDataText={
<span className="text-xs text-center text-neutral-800 dark:text-neutral-200">
{t('No data')}
</span>
}
/>
)}
</AsyncRenderer>
-1
View File
@@ -1,5 +1,4 @@
export * from './order-data-provider';
export * from './order-feedback';
export * from './order-list';
export * from './order-list-manager';
export * from './order-list-container';
@@ -1 +0,0 @@
export * from './order-feedback';
@@ -1,83 +0,0 @@
import { render, screen } from '@testing-library/react';
import * as Schema from '@vegaprotocol/types';
import { VegaTxStatus } from '@vegaprotocol/wallet';
import type { OrderEventFieldsFragment } from '../../order-hooks';
import { generateOrder } from '../mocks/generate-orders';
import type { OrderFeedbackProps } from './order-feedback';
import { OrderFeedback } from './order-feedback';
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({
VEGA_EXPLORER_URL: 'https://test.explorer.vega.network',
}),
}));
describe('OrderFeedback', () => {
let props: OrderFeedbackProps;
beforeEach(() => {
props = {
transaction: {
dialogOpen: false,
status: VegaTxStatus.Complete,
error: null,
txHash: 'tx-hash',
signature: null,
},
order: null,
};
});
it('renders null if no order provided', () => {
const { container } = render(<OrderFeedback {...props} />);
expect(container).toBeEmptyDOMElement();
});
it('renders error reason', () => {
const orderFields = {
status: Schema.OrderStatus.STATUS_REJECTED,
rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_AMEND_FAILURE,
};
const order = generateOrder(orderFields) as OrderEventFieldsFragment;
render(<OrderFeedback {...props} order={order} />);
expect(screen.getByTestId('error-reason')).toHaveTextContent(
`${Schema.OrderRejectionReasonMapping[orderFields.rejectionReason]}`
);
});
it('should render order details when order is placed successfully', () => {
const order = generateOrder({
type: Schema.OrderType.TYPE_LIMIT,
price: '100',
size: '200',
side: Schema.Side.SIDE_BUY,
market: {
decimalPlaces: 2,
positionDecimalPlaces: 0,
},
}) as OrderEventFieldsFragment;
render(<OrderFeedback {...props} order={order} />);
expect(screen.getByTestId('order-confirmed')).toBeInTheDocument();
expect(screen.getByTestId('tx-block-explorer')).toHaveTextContent(
// eslint-disable-next-line
props.transaction.txHash!
);
expect(screen.getByTestId('tx-block-explorer')).toHaveTextContent(
// eslint-disable-next-line
props.transaction.txHash!
);
expect(screen.getByText('Market').nextElementSibling).toHaveTextContent(
// eslint-disable-next-line
order.market!.tradableInstrument.instrument.name
);
expect(screen.getByText('Status').nextElementSibling).toHaveTextContent(
Schema.OrderStatusMapping[order.status]
);
expect(screen.getByText('Price').nextElementSibling).toHaveTextContent(
'1.00'
);
expect(screen.getByText('Size').nextElementSibling).toHaveTextContent(
`+200`
);
});
});
@@ -1,94 +0,0 @@
import { useEnvironment } from '@vegaprotocol/environment';
import type { OrderEventFieldsFragment } from '../../order-hooks/__generated__/OrderEvent';
import { addDecimalsFormatNumber, Size, t } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import type { VegaTxState } from '@vegaprotocol/wallet';
import { Link } from '@vegaprotocol/ui-toolkit';
export interface OrderFeedbackProps {
transaction: VegaTxState;
order: OrderEventFieldsFragment | null;
}
export const OrderFeedback = ({ transaction, order }: OrderFeedbackProps) => {
const { VEGA_EXPLORER_URL } = useEnvironment();
const labelClass = 'font-bold text-black dark:text-white capitalize';
if (!order) return null;
const orderRejectionReason = getRejectionReason(order);
return (
<div data-testid="order-confirmed" className="w-full">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
{order.market && (
<div>
<p className={labelClass}>{t(`Market`)}</p>
<p>{t(`${order.market.tradableInstrument.instrument.name}`)}</p>
</div>
)}
<div>
<p className={labelClass}>{t(`Status`)}</p>
<p>{t(`${Schema.OrderStatusMapping[order.status]}`)}</p>
</div>
{order.type === Schema.OrderType.TYPE_LIMIT && order.market && (
<div>
<p className={labelClass}>{t(`Price`)}</p>
<p>
{addDecimalsFormatNumber(order.price, order.market.decimalPlaces)}
</p>
</div>
)}
<div>
<p className={labelClass}>{t(`Size`)}</p>
<p>
<Size
value={order.size}
side={order.side}
positionDecimalPlaces={order.market.positionDecimalPlaces}
/>
</p>
</div>
</div>
<div className="grid gap-8 mb-8">
{transaction.txHash && (
<div>
<p className={labelClass}>{t('Transaction')}</p>
<Link
style={{ wordBreak: 'break-word' }}
data-testid="tx-block-explorer"
href={`${VEGA_EXPLORER_URL}/txs/0x${transaction.txHash}`}
target="_blank"
rel="noreferrer"
>
{transaction.txHash}
</Link>
</div>
)}
{orderRejectionReason && (
<div>
<p className={labelClass}>{t(`Reason`)}</p>
<p data-testid="error-reason">{t(orderRejectionReason)}</p>
</div>
)}
</div>
</div>
);
};
export const getRejectionReason = (
order: OrderEventFieldsFragment
): string | null => {
switch (order.status) {
case Schema.OrderStatus.STATUS_STOPPED:
return t(
`Your ${
Schema.OrderTimeInForceMapping[order.timeInForce]
} order was not filled and it has been stopped`
);
default:
return order.rejectionReason
? t(Schema.OrderRejectionReasonMapping[order.rejectionReason])
: null;
}
};
@@ -19,56 +19,58 @@ const generateJsx = () => {
);
};
it('Renders a loading state while awaiting orders', async () => {
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [],
loading: true,
error: undefined,
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: 0,
describe('OrderListManager', () => {
it('should render a loading state while awaiting orders', async () => {
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [],
loading: true,
error: undefined,
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: 0,
});
await act(async () => {
render(generateJsx());
});
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
await act(async () => {
render(generateJsx());
});
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('Renders an error state', async () => {
const errorMsg = 'Oops! An Error';
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [],
loading: false,
error: new Error(errorMsg),
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: undefined,
it('should render an error state', async () => {
const errorMsg = 'Oops! An Error';
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: null,
loading: false,
error: new Error(errorMsg),
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: undefined,
});
await act(async () => {
render(generateJsx());
});
expect(
screen.getByText(`Something went wrong: ${errorMsg}`)
).toBeInTheDocument();
});
await act(async () => {
render(generateJsx());
});
expect(
screen.getByText(`Something went wrong: ${errorMsg}`)
).toBeInTheDocument();
});
it('Renders the order list if orders provided', async () => {
// @ts-ignore Orderlist is read only but we need to override with the forwardref to
// avoid warnings about padding refs
orderListMock.OrderListTable = forwardRef(() => <div>OrderList</div>);
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [{ id: '1' } as OrderFieldsFragment],
loading: false,
error: undefined,
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: undefined,
it('should render the order list if orders provided', async () => {
// @ts-ignore OrderList is read only but we need to override with the forwardRef to
// avoid warnings about padding refs
orderListMock.OrderListTable = forwardRef(() => <div>OrderList</div>);
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [{ id: '1' } as OrderFieldsFragment],
loading: false,
error: undefined,
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: undefined,
});
await act(async () => {
render(generateJsx());
});
expect(await screen.findByText('OrderList')).toBeInTheDocument();
});
await act(async () => {
render(generateJsx());
});
expect(await screen.findByText('OrderList')).toBeInTheDocument();
});
@@ -23,7 +23,7 @@ import {
} from '@vegaprotocol/wallet';
import type { VegaTxState, TransactionResult } from '@vegaprotocol/wallet';
import { OrderEditDialog } from '../order-list/order-edit-dialog';
import type { OrderEventFieldsFragment } from '../../order-hooks';
import type { OrderSubFieldsFragment } from '../../order-hooks';
import * as Schema from '@vegaprotocol/types';
import type { Order } from '../order-data-provider';
@@ -238,7 +238,7 @@ export const getCancelDialogIntent = ({
cancelledOrder,
transactionResult,
}: {
cancelledOrder: OrderEventFieldsFragment | null;
cancelledOrder: OrderSubFieldsFragment | null;
transactionResult?: TransactionResult;
}): Intent | undefined => {
if (cancelledOrder) {
@@ -260,7 +260,7 @@ export const getCancelDialogTitle = ({
cancelledOrder,
transactionResult,
}: {
cancelledOrder: OrderEventFieldsFragment | null;
cancelledOrder: OrderSubFieldsFragment | null;
transactionResult?: TransactionResult;
}): string | undefined => {
if (cancelledOrder) {
@@ -1,33 +0,0 @@
fragment OrderEventFields on Order {
type
id
status
rejectionReason
createdAt
size
price
timeInForce
expiresAt
side
market {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
}
}
}
}
subscription OrderEvent($partyId: ID!) {
busEvents(partyId: $partyId, batchSize: 0, types: [Order]) {
type
event {
... on Order {
...OrderEventFields
}
}
}
}
@@ -0,0 +1,19 @@
fragment OrderSubFields on OrderUpdate {
type
id
status
rejectionReason
createdAt
size
price
timeInForce
expiresAt
side
marketId
}
subscription OrderSub($partyId: ID!) {
orders(partyId: $partyId) {
...OrderSubFields
}
}
@@ -1,73 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type OrderEventFieldsFragment = { __typename?: 'Order', type?: Types.OrderType | null, id: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, createdAt: any, size: string, price: string, timeInForce: Types.OrderTimeInForce, expiresAt?: any | null, side: Types.Side, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } };
export type OrderEventSubscriptionVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type OrderEventSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', type: Types.BusEventType, event: { __typename?: 'AccountEvent' } | { __typename?: 'Asset' } | { __typename?: 'AuctionEvent' } | { __typename?: 'Deposit' } | { __typename?: 'LiquidityProvision' } | { __typename?: 'LossSocialization' } | { __typename?: 'MarginLevels' } | { __typename?: 'Market' } | { __typename?: 'MarketData' } | { __typename?: 'MarketEvent' } | { __typename?: 'MarketTick' } | { __typename?: 'NodeSignature' } | { __typename?: 'OracleSpec' } | { __typename?: 'Order', type?: Types.OrderType | null, id: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, createdAt: any, size: string, price: string, timeInForce: Types.OrderTimeInForce, expiresAt?: any | null, side: Types.Side, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } } | { __typename?: 'Party' } | { __typename?: 'PositionResolution' } | { __typename?: 'Proposal' } | { __typename?: 'RiskFactor' } | { __typename?: 'SettleDistressed' } | { __typename?: 'SettlePosition' } | { __typename?: 'TimeUpdate' } | { __typename?: 'Trade' } | { __typename?: 'TransactionResult' } | { __typename?: 'TransferResponses' } | { __typename?: 'Vote' } | { __typename?: 'Withdrawal' } }> | null };
export const OrderEventFieldsFragmentDoc = gql`
fragment OrderEventFields on Order {
type
id
status
rejectionReason
createdAt
size
price
timeInForce
expiresAt
side
market {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
}
}
}
}
`;
export const OrderEventDocument = gql`
subscription OrderEvent($partyId: ID!) {
busEvents(partyId: $partyId, batchSize: 0, types: [Order]) {
type
event {
... on Order {
...OrderEventFields
}
}
}
}
${OrderEventFieldsFragmentDoc}`;
/**
* __useOrderEventSubscription__
*
* To run a query within a React component, call `useOrderEventSubscription` and pass it any options that fit your needs.
* When your component renders, `useOrderEventSubscription` 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 } = useOrderEventSubscription({
* variables: {
* partyId: // value for 'partyId'
* },
* });
*/
export function useOrderEventSubscription(baseOptions: Apollo.SubscriptionHookOptions<OrderEventSubscription, OrderEventSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<OrderEventSubscription, OrderEventSubscriptionVariables>(OrderEventDocument, options);
}
export type OrderEventSubscriptionHookResult = ReturnType<typeof useOrderEventSubscription>;
export type OrderEventSubscriptionResult = Apollo.SubscriptionResult<OrderEventSubscription>;
@@ -0,0 +1,59 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type OrderSubFieldsFragment = { __typename?: 'OrderUpdate', type?: Types.OrderType | null, id: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, createdAt: any, size: string, price: string, timeInForce: Types.OrderTimeInForce, expiresAt?: any | null, side: Types.Side, marketId: string };
export type OrderSubSubscriptionVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type OrderSubSubscription = { __typename?: 'Subscription', orders?: Array<{ __typename?: 'OrderUpdate', type?: Types.OrderType | null, id: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, createdAt: any, size: string, price: string, timeInForce: Types.OrderTimeInForce, expiresAt?: any | null, side: Types.Side, marketId: string }> | null };
export const OrderSubFieldsFragmentDoc = gql`
fragment OrderSubFields on OrderUpdate {
type
id
status
rejectionReason
createdAt
size
price
timeInForce
expiresAt
side
marketId
}
`;
export const OrderSubDocument = gql`
subscription OrderSub($partyId: ID!) {
orders(partyId: $partyId) {
...OrderSubFields
}
}
${OrderSubFieldsFragmentDoc}`;
/**
* __useOrderSubSubscription__
*
* To run a query within a React component, call `useOrderSubSubscription` and pass it any options that fit your needs.
* When your component renders, `useOrderSubSubscription` 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 } = useOrderSubSubscription({
* variables: {
* partyId: // value for 'partyId'
* },
* });
*/
export function useOrderSubSubscription(baseOptions: Apollo.SubscriptionHookOptions<OrderSubSubscription, OrderSubSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<OrderSubSubscription, OrderSubSubscriptionVariables>(OrderSubDocument, options);
}
export type OrderSubSubscriptionHookResult = ReturnType<typeof useOrderSubSubscription>;
export type OrderSubSubscriptionResult = Apollo.SubscriptionResult<OrderSubSubscription>;
+2 -2
View File
@@ -1,7 +1,7 @@
export * from './__generated__/OrderEvent';
export * from './__generated__/OrdersSubscription';
export * from './use-has-active-order';
export * from './use-order-cancel';
export * from './use-order-submit';
export * from './use-order-edit';
export * from './use-order-event';
export * from './use-order-update';
export * from './use-persisted-order';
@@ -5,8 +5,8 @@ import type { ReactNode } from 'react';
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { useOrderCancel } from './use-order-cancel';
import type { OrderEventSubscription } from './';
import { OrderEventDocument } from './';
import type { OrderSubSubscription } from './';
import { OrderSubDocument } from './';
import * as Schema from '@vegaprotocol/types';
const defaultWalletContext = {
@@ -21,89 +21,57 @@ const defaultWalletContext = {
};
function setup(context?: Partial<VegaWalletContextShape>) {
const mocks: MockedResponse<OrderEventSubscription> = {
const mocks: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderEventDocument,
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
busEvents: [
orders: [
{
type: Schema.BusEventType.Order,
event: {
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
market: {
id: 'market-id',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
name: 'UNIDAI Monthly (30 Jun 2022)',
__typename: 'Instrument',
},
},
__typename: 'Market',
},
__typename: 'Order',
},
__typename: 'BusEvent',
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
marketId: 'market-id',
__typename: 'OrderUpdate',
},
],
},
},
};
const filterMocks: MockedResponse<OrderEventSubscription> = {
const filterMocks: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderEventDocument,
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
busEvents: [
orders: [
{
type: Schema.BusEventType.Order,
event: {
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
market: {
id: 'market-id',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
name: 'UNIDAI Monthly (30 Jun 2022)',
__typename: 'Instrument',
},
},
__typename: 'Market',
},
__typename: 'Order',
},
__typename: 'BusEvent',
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
marketId: 'market-id',
__typename: 'OrderUpdate',
},
],
},
@@ -8,15 +8,15 @@ import type {
OrderCancellationBody,
TransactionResult,
} from '@vegaprotocol/wallet';
import type { OrderEventFieldsFragment } from './';
import type { OrderSubFieldsFragment } from './';
import * as Sentry from '@sentry/react';
import { useOrderEvent } from './use-order-event';
import { useOrderUpdate } from './use-order-update';
export const useOrderCancel = () => {
const { pubKey } = useVegaWallet();
const [cancelledOrder, setCancelledOrder] =
useState<OrderEventFieldsFragment | null>(null);
useState<OrderSubFieldsFragment | null>(null);
const [transactionResult, setTransactionResult] =
useState<TransactionResult>();
@@ -28,7 +28,7 @@ export const useOrderCancel = () => {
Dialog,
} = useVegaTransaction();
const waitForOrderEvent = useOrderEvent(transaction);
const waitForOrderUpdate = useOrderUpdate(transaction);
const waitForTransactionResult = useTransactionResult();
const reset = useCallback(() => {
@@ -49,7 +49,7 @@ export const useOrderCancel = () => {
orderCancellation,
});
if (orderCancellation.orderId) {
const cancelledOrder = await waitForOrderEvent(
const cancelledOrder = await waitForOrderUpdate(
orderCancellation.orderId,
pubKey
);
@@ -69,7 +69,7 @@ export const useOrderCancel = () => {
return;
}
},
[pubKey, send, setComplete, waitForOrderEvent, waitForTransactionResult]
[pubKey, send, setComplete, waitForOrderUpdate, waitForTransactionResult]
);
return {
@@ -3,8 +3,8 @@ import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
import type { ReactNode } from 'react';
import { useOrderEdit } from './use-order-edit';
import type { OrderEventSubscription } from './__generated__/OrderEvent';
import { OrderEventDocument } from './__generated__/OrderEvent';
import type { OrderSubSubscription } from './__generated__/OrdersSubscription';
import { OrderSubDocument } from './__generated__/OrdersSubscription';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import type { Order } from '../components';
@@ -23,89 +23,57 @@ const defaultWalletContext = {
};
function setup(order: Order, context?: Partial<VegaWalletContextShape>) {
const mocks: MockedResponse<OrderEventSubscription> = {
const mocks: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderEventDocument,
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
busEvents: [
orders: [
{
type: Schema.BusEventType.Order,
event: {
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
market: {
id: 'market-id',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
name: 'UNIDAI Monthly (30 Jun 2022)',
__typename: 'Instrument',
},
},
__typename: 'Market',
},
__typename: 'Order',
},
__typename: 'BusEvent',
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
marketId: 'market-id',
__typename: 'OrderUpdate',
},
],
},
},
};
const filterMocks: MockedResponse<OrderEventSubscription> = {
const filterMocks: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderEventDocument,
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
busEvents: [
orders: [
{
type: Schema.BusEventType.Order,
event: {
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
market: {
id: 'market-id',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
name: 'UNIDAI Monthly (30 Jun 2022)',
__typename: 'Instrument',
},
},
__typename: 'Market',
},
__typename: 'Order',
},
__typename: 'BusEvent',
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
marketId: 'market-id',
__typename: 'OrderUpdate',
},
],
},
@@ -1,10 +1,10 @@
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/react-helpers';
import { useState, useCallback } from 'react';
import { useVegaTransaction, useVegaWallet } from '@vegaprotocol/wallet';
import type { OrderEventFieldsFragment } from './';
import type { OrderSubFieldsFragment } from './';
import * as Sentry from '@sentry/react';
import type { Order } from '../components';
import { useOrderEvent } from './use-order-event';
import { useOrderUpdate } from './use-order-update';
import BigNumber from 'bignumber.js';
export interface EditOrderArgs {
@@ -16,7 +16,7 @@ export const useOrderEdit = (order: Order | null) => {
const { pubKey } = useVegaWallet();
const [updatedOrder, setUpdatedOrder] =
useState<OrderEventFieldsFragment | null>(null);
useState<OrderSubFieldsFragment | null>(null);
const {
send,
@@ -26,7 +26,7 @@ export const useOrderEdit = (order: Order | null) => {
Dialog,
} = useVegaTransaction();
const waitForOrderEvent = useOrderEvent(transaction);
const waitForOrderUpdate = useOrderUpdate(transaction);
const reset = useCallback(() => {
resetTransaction();
@@ -61,7 +61,7 @@ export const useOrderEdit = (order: Order | null) => {
},
});
const updatedOrder = await waitForOrderEvent(order.id, pubKey);
const updatedOrder = await waitForOrderUpdate(order.id, pubKey);
setUpdatedOrder(updatedOrder);
setComplete();
} catch (e) {
@@ -69,7 +69,7 @@ export const useOrderEdit = (order: Order | null) => {
return;
}
},
[pubKey, send, order, setComplete, waitForOrderEvent]
[pubKey, send, order, setComplete, waitForOrderUpdate]
);
return {
@@ -1,68 +0,0 @@
import { useApolloClient } from '@apollo/client';
import { useCallback, useEffect, useRef } from 'react';
import { OrderEventDocument } from './__generated__/OrderEvent';
import type {
OrderEventSubscription,
OrderEventSubscriptionVariables,
OrderEventFieldsFragment,
} from './__generated__/OrderEvent';
import type { Subscription } from 'zen-observable-ts';
import type { VegaTxState } from '@vegaprotocol/wallet';
import * as Schema from '@vegaprotocol/types';
type WaitFunc = (
orderId: string,
partyId: string
) => Promise<OrderEventFieldsFragment>;
export const useOrderEvent = (transaction: VegaTxState) => {
const client = useApolloClient();
const subRef = useRef<Subscription | null>(null);
const waitForOrderEvent = useCallback<WaitFunc>(
(id: string, partyId: string) => {
return new Promise((resolve) => {
subRef.current = client
.subscribe<OrderEventSubscription, OrderEventSubscriptionVariables>({
query: OrderEventDocument,
variables: { partyId },
})
.subscribe(({ data }) => {
if (!data?.busEvents?.length) {
return;
}
// No types available for the subscription result
const matchingOrderEvent = data.busEvents.find((e) => {
if (e.event.__typename !== Schema.BusEventType.Order) {
return false;
}
return e.event.id === id;
});
if (
matchingOrderEvent &&
matchingOrderEvent.event.__typename === Schema.BusEventType.Order
) {
resolve(matchingOrderEvent.event);
subRef.current?.unsubscribe();
}
});
});
},
[client]
);
useEffect(() => {
if (!transaction.dialogOpen) {
subRef.current?.unsubscribe();
}
return () => {
subRef.current?.unsubscribe();
};
}, [transaction.dialogOpen]);
return waitForOrderEvent;
};
@@ -5,8 +5,8 @@ import * as Schema from '@vegaprotocol/types';
import type { ReactNode } from 'react';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useOrderSubmit } from './use-order-submit';
import type { OrderEventSubscription } from './';
import { OrderEventDocument } from './';
import type { OrderSubSubscription } from './';
import { OrderSubDocument } from './';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
@@ -48,89 +48,56 @@ const defaultWalletContext = {
};
function setup(context?: Partial<VegaWalletContextShape>) {
const mocks: MockedResponse<OrderEventSubscription> = {
const mocks: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderEventDocument,
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
busEvents: [
orders: [
{
type: Schema.BusEventType.Order,
event: {
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
market: {
id: 'market-id',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
name: 'UNIDAI Monthly (30 Jun 2022)',
__typename: 'Instrument',
},
},
__typename: 'Market',
},
__typename: 'Order',
},
__typename: 'BusEvent',
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
marketId: 'market-id',
},
],
},
},
};
const filterMocks: MockedResponse<OrderEventSubscription> = {
const filterMocks: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderEventDocument,
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
busEvents: [
orders: [
{
type: Schema.BusEventType.Order,
event: {
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
market: {
id: 'market-id',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
name: 'UNIDAI Monthly (30 Jun 2022)',
__typename: 'Instrument',
},
},
__typename: 'Market',
},
__typename: 'Order',
},
__typename: 'BusEvent',
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
marketId: 'market-id',
__typename: 'OrderUpdate',
},
],
},
@@ -1,13 +1,13 @@
import { useCallback, useState } from 'react';
import type { ReactNode } from 'react';
import type { OrderEventFieldsFragment } from './__generated__/OrderEvent';
import type { OrderSubFieldsFragment } from './__generated__/OrdersSubscription';
import {
useVegaWallet,
useVegaTransaction,
determineId,
} from '@vegaprotocol/wallet';
import * as Sentry from '@sentry/react';
import { useOrderEvent } from './use-order-event';
import { useOrderUpdate } from './use-order-update';
import * as Schema from '@vegaprotocol/types';
import { Icon, Intent } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
@@ -96,10 +96,10 @@ export const useOrderSubmit = () => {
Dialog,
} = useVegaTransaction();
const waitForOrderEvent = useOrderEvent(transaction);
const waitForOrderUpdate = useOrderUpdate(transaction);
const [finalizedOrder, setFinalizedOrder] =
useState<OrderEventFieldsFragment | null>(null);
useState<OrderSubFieldsFragment | null>(null);
const reset = useCallback(() => {
resetTransaction();
@@ -120,7 +120,7 @@ export const useOrderSubmit = () => {
if (res) {
const orderId = determineId(res.signature);
if (orderId) {
const order = await waitForOrderEvent(orderId, pubKey);
const order = await waitForOrderUpdate(orderId, pubKey);
setFinalizedOrder(order);
setComplete();
}
@@ -129,7 +129,7 @@ export const useOrderSubmit = () => {
Sentry.captureException(e);
}
},
[pubKey, send, setComplete, waitForOrderEvent]
[pubKey, send, setComplete, waitForOrderUpdate]
);
return {
@@ -0,0 +1,60 @@
import { useApolloClient } from '@apollo/client';
import { useCallback, useEffect, useRef } from 'react';
import { OrderSubDocument } from './__generated__/OrdersSubscription';
import type {
OrderSubSubscription,
OrderSubSubscriptionVariables,
OrderSubFieldsFragment,
} from './__generated__/OrdersSubscription';
import type { Subscription } from 'zen-observable-ts';
import type { VegaTxState } from '@vegaprotocol/wallet';
type WaitFunc = (
orderId: string,
partyId: string
) => Promise<OrderSubFieldsFragment>;
export const useOrderUpdate = (transaction: VegaTxState) => {
const client = useApolloClient();
const subRef = useRef<Subscription | null>(null);
const waitForOrderUpdate = useCallback<WaitFunc>(
(id: string, partyId: string) => {
return new Promise((resolve) => {
subRef.current = client
.subscribe<OrderSubSubscription, OrderSubSubscriptionVariables>({
query: OrderSubDocument,
variables: { partyId },
})
.subscribe(({ data }) => {
if (!data?.orders?.length) {
return;
}
// No types available for the subscription result
const matchingOrder = data.orders.find((order) => {
return order.id === id;
});
if (matchingOrder) {
resolve(matchingOrder);
subRef.current?.unsubscribe();
}
});
});
},
[client]
);
useEffect(() => {
if (!transaction.dialogOpen) {
subRef.current?.unsubscribe();
}
return () => {
subRef.current?.unsubscribe();
};
}, [transaction.dialogOpen]);
return waitForOrderUpdate;
};
+18
View File
@@ -1,5 +1,6 @@
import { t } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import type { OrderSubFieldsFragment } from './order-hooks';
// More detail in https://docs.vega.xyz/mainnet/graphql/enums/order-time-in-force
export const timeInForceLabel = (tif: string) => {
@@ -20,3 +21,20 @@ export const timeInForceLabel = (tif: string) => {
return t(tif);
}
};
export const getRejectionReason = (
order: OrderSubFieldsFragment
): string | null => {
switch (order.status) {
case Schema.OrderStatus.STATUS_STOPPED:
return t(
`Your ${
Schema.OrderTimeInForceMapping[order.timeInForce]
} order was not filled and it has been stopped`
);
default:
return order.rejectionReason
? t(Schema.OrderRejectionReasonMapping[order.rejectionReason])
: null;
}
};
@@ -335,7 +335,6 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
? undefined
: addDecimalsFormatNumber(data.realisedPNL, data.decimals);
}}
cellRenderer="PriceFlashCell"
headerTooltip={t(
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
)}
@@ -360,7 +359,6 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
? undefined
: addDecimalsFormatNumber(data.unrealisedPNL, data.decimals)
}
cellRenderer="PriceFlashCell"
headerTooltip={t(
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
)}
@@ -10,8 +10,8 @@ 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 { OrderEventSubscription } from '@vegaprotocol/orders';
import { OrderEventDocument } from '@vegaprotocol/orders';
import type { OrderSubSubscription } from '@vegaprotocol/orders';
import { OrderSubDocument } from '@vegaprotocol/orders';
const pubKey = 'test-pubkey';
const defaultWalletContext = {
@@ -52,45 +52,29 @@ function setup(context?: Partial<VegaWalletContextShape>) {
},
},
};
const mockOrderResult: MockedResponse<OrderEventSubscription> = {
const mockOrderResult: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderEventDocument,
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
busEvents: [
orders: [
{
type: Types.BusEventType.Order,
event: {
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,
market: {
id: 'market-id',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
name: 'UNIDAI Monthly (30 Jun 2022)',
__typename: 'Instrument',
},
},
__typename: 'Market',
},
__typename: 'Order',
},
__typename: 'BusEvent',
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',
},
],
},
+4 -4
View File
@@ -5,8 +5,8 @@ import { useVegaWallet, useTransactionResult } from '@vegaprotocol/wallet';
import { useVegaTransaction } from '@vegaprotocol/wallet';
import * as Sentry from '@sentry/react';
import * as Schema from '@vegaprotocol/types';
import { useOrderEvent } from '@vegaprotocol/orders';
import type { OrderEventFieldsFragment } from '@vegaprotocol/orders';
import { useOrderUpdate } from '@vegaprotocol/orders';
import type { OrderSubFieldsFragment } from '@vegaprotocol/orders';
export interface ClosingOrder {
marketId: string;
@@ -21,11 +21,11 @@ export const useClosePosition = () => {
const { send, transaction, setComplete, Dialog } = useVegaTransaction();
const [closingOrder, setClosingOrder] = useState<ClosingOrder>();
const [closingOrderResult, setClosingOrderResult] =
useState<OrderEventFieldsFragment>();
useState<OrderSubFieldsFragment>();
const [transactionResult, setTransactionResult] =
useState<TransactionResult>();
const waitForTransactionResult = useTransactionResult();
const waitForOrder = useOrderEvent(transaction);
const waitForOrder = useOrderUpdate(transaction);
const submit = useCallback(
async ({
@@ -2,6 +2,7 @@ import { renderHook, waitFor } from '@testing-library/react';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import type { NetworkParamsKey } from './use-network-params';
import { toRealKey } from './use-network-params';
import {
NetworkParams,
useNetworkParam,
@@ -18,7 +19,7 @@ describe('useNetworkParam', () => {
request: {
query: NetworkParamDocument,
variables: {
key: arg,
key: toRealKey(arg),
},
},
result: {
@@ -104,10 +104,11 @@ export const NetworkParams = {
market_liquidity_stakeToCcyVolume: 'market_liquidity_stakeToCcyVolume',
market_liquidity_targetstake_triggering_ratio:
'market_liquidity_targetstake_triggering_ratio',
transfer_fee_factor: 'transfer_fee_factor',
} as const;
type Params = typeof NetworkParams;
export type NetworkParamsKey = Params[keyof Params];
export type NetworkParamsKey = keyof Params;
type Result = {
[key in keyof Params]: string;
};
@@ -120,7 +121,7 @@ export const useNetworkParams = <T extends NetworkParamsKey[]>(params?: T) => {
return compact(data.networkParametersConnection.edges)
.map((p) => ({
...p.node,
key: p.node.key.split('.').join('_'),
key: toInternalKey(p.node.key),
}))
.filter((p) => {
if (params === undefined || params.length === 0) return true;
@@ -143,7 +144,7 @@ export const useNetworkParams = <T extends NetworkParamsKey[]>(params?: T) => {
export const useNetworkParam = (param: NetworkParamsKey) => {
const { data, loading, error } = useNetworkParamQuery({
variables: {
key: param,
key: toRealKey(param),
},
});
@@ -153,3 +154,11 @@ export const useNetworkParam = (param: NetworkParamsKey) => {
error,
};
};
export const toRealKey = (key: NetworkParamsKey) => {
return key.split('_').join('.');
};
export const toInternalKey = (key: string) => {
return key.split('.').join('_');
};
+13 -8
View File
@@ -99,20 +99,20 @@ module.exports = {
// DARK
dark: {
400: '#161616',
300: '#262626',
100: '#161616',
150: '#262626',
200: '#404040',
150: '#8B8B8B',
100: '#C0C0C0',
300: '#8B8B8B',
400: '#C0C0C0',
},
// LIGHT
light: {
400: '#F0F0F0',
300: '#E9E9E9',
100: '#F0F0F0',
150: '#E9E9E9',
200: '#D2D2D2',
150: '#939393',
100: '#626262',
300: '#939393',
400: '#626262',
},
},
danger: '#FF077F',
@@ -174,11 +174,16 @@ module.exports = {
'60%': { transform: 'rotate( 0.0deg)' },
'100%': { transform: 'rotate( 0.0deg)' },
},
progress: {
from: { width: '0' },
to: { width: '100%' },
},
},
animation: {
rotate: 'rotate 2s linear alternate infinite',
'rotate-back': 'rotate 2s linear reverse infinite',
wave: 'wave 2s linear infinite',
progress: 'progress 5s cubic-bezier(.39,.58,.57,1) 1',
},
data: {
selected: 'state~="checked"',
@@ -10,6 +10,9 @@ const vegaCustomClasses = plugin(function ({ addUtilities }) {
'.liga-0-calt-0': {
fontFeatureSettings: "'liga' 0, 'calt' 0",
},
'.liga': {
fontFeatureSettings: "'liga'",
},
'.syntax-highlighter-wrapper .hljs': {
fontSize: '1rem',
fontFamily: "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace",
-4
View File
@@ -3894,10 +3894,6 @@ export type Statistics = {
chainVersion: Scalars['String'];
/** RFC3339Nano current time (real) */
currentTime: Scalars['Timestamp'];
/** Total number of events on the last block */
eventCount: Scalars['String'];
/** The number of events per second on the last block */
eventsPerSecond: Scalars['String'];
/** RFC3339Nano genesis time of the chain */
genesisTime: Scalars['Timestamp'];
/** Number of orders per seconds */
@@ -1,6 +1,7 @@
import { Splash } from '../splash';
import type { ReactNode } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import * as Sentry from '@sentry/react';
interface AsyncRendererProps<T> {
loading: boolean;
@@ -26,13 +27,16 @@ export function AsyncRenderer<T = object>({
render,
}: AsyncRendererProps<T>) {
if (error) {
return (
<Splash>
{errorMessage
? errorMessage
: t(`Something went wrong: ${error.message}`)}
</Splash>
);
Sentry.captureException(`Error rendering data: ${error.message}`);
if (!data) {
return (
<Splash>
{errorMessage
? errorMessage
: t(`Something went wrong: ${error.message}`)}
</Splash>
);
}
}
if (loading) {
@@ -151,7 +151,7 @@ export const DropdownMenuSeparator = forwardRef<
{...separatorProps}
ref={forwardedRef}
className={classNames(
'h-px my-1 mx-2 bg-neutral-700 dark:bg-black',
'h-px my-1 mx-2 bg-neutral-400 dark:bg-neutral-300',
className
)}
/>

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